pi-slideshow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +23 -0
- data/README.md +126 -0
- data/bin/fim-shell +31 -0
- data/bin/pi-slides +79 -0
- data/lib/pi-slides.rb +1 -0
- data/lib/pi_slides.rb +23 -0
- data/lib/pi_slides/fim.rb +60 -0
- data/lib/pi_slides/image_dir.rb +21 -0
- data/lib/pi_slides/slideshow.rb +92 -0
- data/lib/pi_slides/version.rb +3 -0
- data/lib/pi_slides/web_frontend.rb +46 -0
- data/public/jquery-2.1.0.min.js +4 -0
- data/public/jquery.mobile-1.4.2.js +15056 -0
- data/public/jquery.mobile-1.4.2.min.css +3 -0
- data/public/jquery.mobile-1.4.2.min.js +10 -0
- data/public/pi_slides.js +10 -0
- data/r2.md +30 -0
- data/views/index.erb +36 -0
- data/views/layout.erb +21 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fb62ebde2f8105c75f47676222be4a8cefe39e12
|
4
|
+
data.tar.gz: 2c5529831be525c682a7ab2ab4849ef60da0432d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ae87d936cbf68a657350ffd3dd713a8b63e565178f51dff0c3caac0e0f757f9f04928e9e0c2da9013a1c631f82bd0411e12203e090bf753035e7da5f0e732cc
|
7
|
+
data.tar.gz: 8c29b7f518d6b7d4a6f557fc735fb8d41a281f814c44723ef999b877fb2ad7868c9773ef964f916f2da84e3664e71abc5ea6a36e24811e37f674b197a03c4139
|
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2014 Jens Krämer
|
2
|
+
|
3
|
+
The MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# Pi-Slideshow
|
2
|
+
|
3
|
+
A slideshow application for the Raspberry Pi or similar setups involving Linux
|
4
|
+
and a framebuffer.
|
5
|
+
|
6
|
+
|
7
|
+
## Setup
|
8
|
+
|
9
|
+
We will use [fim](http://www.autistici.org/dezperado/fim/) to display images,
|
10
|
+
netcat for remote controlling fim, and Ruby for everything else.
|
11
|
+
|
12
|
+
On [Raspbian](http://www.raspbian.org/) you should be all set with
|
13
|
+
|
14
|
+
```
|
15
|
+
aptitude install fim netcat ruby1.9.1 ruby-dev libssl-dev
|
16
|
+
```
|
17
|
+
.
|
18
|
+
|
19
|
+
Any Ruby dependencies (basically that's Sinatra and Puma) will be pulled in
|
20
|
+
once you install the gem:
|
21
|
+
|
22
|
+
```
|
23
|
+
gem install pi-slideshow
|
24
|
+
```
|
25
|
+
|
26
|
+
This gives you @/usr/local/bin/pi-slides@:
|
27
|
+
|
28
|
+
```
|
29
|
+
pi@raspberrypi ~ $ pi-slides --help
|
30
|
+
Usage:
|
31
|
+
pi-slides [options]
|
32
|
+
where [options] are:
|
33
|
+
--path, -p <s>: image base directory (default: .)
|
34
|
+
--interval, -i <i>: seconds between image changes (default: 30)
|
35
|
+
--web, --no-web, -w: enable built in web interface (default: true)
|
36
|
+
--port, -o <i>: listen port for the web interface (default: 4567)
|
37
|
+
--testmode, -t: run against echo server instead launching a real fim
|
38
|
+
--fim-port, -f <i>: port to use for communicating with fim (default: 9999)
|
39
|
+
--fim-binary, -m <s>: path to fim binary (default: /usr/bin/fim)
|
40
|
+
--fim-opts, -s <s>: fim options (default: -d /dev/fb0)
|
41
|
+
--remote-fim, -r <s>: do not spawn fim, instead connect to this ip
|
42
|
+
--verbose, -v: verbose mode
|
43
|
+
--quiet, -q: quiet mode
|
44
|
+
--help, -h: Show this message
|
45
|
+
```
|
46
|
+
|
47
|
+
Most of the time you will only need to bother with the @path@, @port@ and maybe
|
48
|
+
@interval@ options, which tell pi-slides where your images are, which port
|
49
|
+
the web interface should run on, and how long each image should be shown
|
50
|
+
initially.
|
51
|
+
|
52
|
+
Pi-slides scans the path given for JPGs and displays them in random
|
53
|
+
order. Once finished, the path is re-scanned, so any changes (i.e. new
|
54
|
+
pictures) will be picked up.
|
55
|
+
|
56
|
+
For launching the slideshow upon boot, call pi-slides from @/etc/rc.local@:
|
57
|
+
|
58
|
+
```
|
59
|
+
pi@raspberrypi ~ $ cat /etc/rc.local
|
60
|
+
#!/bin/sh -e
|
61
|
+
#
|
62
|
+
# rc.local
|
63
|
+
#
|
64
|
+
# This script is executed at the end of each multiuser runlevel.
|
65
|
+
# Make sure that the script will "exit 0" on success or any other
|
66
|
+
# value on error.
|
67
|
+
#
|
68
|
+
# In order to enable or disable this script just change the execution
|
69
|
+
# bits.
|
70
|
+
#
|
71
|
+
# By default this script does nothing.
|
72
|
+
|
73
|
+
# Print the IP address
|
74
|
+
_IP=$(hostname -I) || true
|
75
|
+
if [ "$_IP" ]; then
|
76
|
+
printf "My IP address is %s\n" "$_IP"
|
77
|
+
fi
|
78
|
+
|
79
|
+
/usr/local/bin/pi-slides --path=/home/pi/images --interval=90 --port=80 > /tmp/pim-slides.log
|
80
|
+
|
81
|
+
exit 0
|
82
|
+
```
|
83
|
+
|
84
|
+
Note that if you call it like this, pi-slides will stay in the foreground,
|
85
|
+
meaning the login prompt is never reached. This is a good thing since otherwise
|
86
|
+
the blinking cursor of the prompt would be very visible all the time.
|
87
|
+
|
88
|
+
Just make sure you can ssh into the Pi before you reboot if you made this
|
89
|
+
change to @rc.local@.
|
90
|
+
|
91
|
+
## Enjoy!
|
92
|
+
|
93
|
+
After a reboot, your Pi should start happily displaying images in random order.
|
94
|
+
|
95
|
+
Soon after all is up and running nicely you will most probably notice that it
|
96
|
+
would be nice to change the slide interval *without* having to ssh into that
|
97
|
+
tiny box. Or to hold the slideshow for a while at your favorite shot. Or to
|
98
|
+
turn of the automatic fill-screen scaling that fim does by default. Here you go
|
99
|
+
- point your browser (preferably on your phone) to port 4567 of your Raspberry
|
100
|
+
Pi and you should see the remote control web interface.
|
101
|
+
|
102
|
+
|
103
|
+
## Disclaimer / Security implications
|
104
|
+
|
105
|
+
Setup like this you will have fim listening on port 9999 (localhost), and a
|
106
|
+
webserver listening on port 4567 (all interfaces), both running as *root* on
|
107
|
+
your Pi.
|
108
|
+
|
109
|
+
Everybody on the same network may access the web frontend, and everybody on the
|
110
|
+
Pi can make fim execute whatever fim script they like. Any security leak in
|
111
|
+
fim's script parser or the puma, sinatra or the slideshow webapp can lead to
|
112
|
+
somebody gaining root on your Pi.
|
113
|
+
|
114
|
+
There are ways to do this better, i.e. it should be possible to run fim as a
|
115
|
+
non-root user without losing access to the framebuffer, and of course puma does
|
116
|
+
not need to run as root (at least as long as you don't want it to bind to a
|
117
|
+
privileged port). It would be awesome if puma could drop privileges after
|
118
|
+
binding to port 80, however I didn't find any information regarding this.
|
119
|
+
|
120
|
+
Whether all this is a risk for you largely depends on your environment - my Pi
|
121
|
+
sits behind a NATting firewall in my home network, and serves no other purpose
|
122
|
+
than showing pictures, that's why I don't care too much. I for sure wouldn't
|
123
|
+
run it like this on a network with people I don't trust.
|
124
|
+
|
125
|
+
|
126
|
+
|
data/bin/fim-shell
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'trollop'
|
4
|
+
require 'pi_slides'
|
5
|
+
|
6
|
+
opts = Trollop::options do
|
7
|
+
banner <<-EOS
|
8
|
+
Usage:
|
9
|
+
fim-shell [options]
|
10
|
+
where [options] are:
|
11
|
+
EOS
|
12
|
+
opt :port, 'port where fim is listening', :short => :p, :default => 9999
|
13
|
+
opt :host, 'host where fim is running', :default => 'localhost'
|
14
|
+
end
|
15
|
+
|
16
|
+
fim = PiSlides::Fim.new opts[:host], opts[:port]
|
17
|
+
|
18
|
+
def prompt
|
19
|
+
print "> "
|
20
|
+
$stdout.flush
|
21
|
+
end
|
22
|
+
|
23
|
+
prompt
|
24
|
+
while cmd = $stdin.readline
|
25
|
+
cmd.strip!
|
26
|
+
exit if %w(quit exit).include? cmd
|
27
|
+
cmd = "#{cmd};" unless cmd =~ /;$/
|
28
|
+
fim.exec cmd
|
29
|
+
prompt
|
30
|
+
end
|
31
|
+
|
data/bin/pi-slides
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'trollop'
|
4
|
+
require 'pi_slides'
|
5
|
+
|
6
|
+
|
7
|
+
opts = Trollop::options do
|
8
|
+
banner <<-EOS
|
9
|
+
Usage:
|
10
|
+
pi-slides [options]
|
11
|
+
where [options] are:
|
12
|
+
EOS
|
13
|
+
opt :path, 'image base directory', :type => :string, :default => '.'
|
14
|
+
opt :interval, 'seconds between image changes', :default => 30
|
15
|
+
opt :web, 'enable built in web interface', :default => true
|
16
|
+
opt :port, 'listen port for the web interface', :default => 4567
|
17
|
+
opt :testmode, 'run against echo server instead launching a real fim', :default => false
|
18
|
+
opt :fim_port, 'port to use for communicating with fim', :default => 9999
|
19
|
+
opt :fim_binary, 'path to fim binary', :default => '/usr/bin/fim'
|
20
|
+
opt :fim_opts, 'fim options', :default => '-d /dev/fb0'
|
21
|
+
opt :remote_fim, 'do not spawn fim, instead connect to this ip', :default => nil, :type => :string
|
22
|
+
opt :verbose, 'verbose mode', :default => false
|
23
|
+
opt :quiet, 'quiet mode', :default => false
|
24
|
+
end
|
25
|
+
|
26
|
+
PiSlides.logger.level = if opts[:quiet]
|
27
|
+
9
|
28
|
+
elsif opts[:verbose]
|
29
|
+
0
|
30
|
+
else
|
31
|
+
1
|
32
|
+
end
|
33
|
+
|
34
|
+
image_path = File.expand_path opts[:path]
|
35
|
+
raise 'invalid image directory' unless File.directory?(image_path)
|
36
|
+
|
37
|
+
fim_host = opts[:remote_fim] || '127.0.0.1'
|
38
|
+
|
39
|
+
unless opts[:remote_fim]
|
40
|
+
# launch fim running a netcat loop, or, for test mode, launch something
|
41
|
+
# pretending to be fim running a netcat loop:
|
42
|
+
netcat_command = "nc -l #{fim_host} #{opts[:fim_port]}"
|
43
|
+
fim_pid = if opts[:testmode]
|
44
|
+
fork do
|
45
|
+
PiSlides.debug "spawned echo server with pid #{Process.pid}"
|
46
|
+
loop do
|
47
|
+
PiSlides.info "fim: #{`#{netcat_command}`}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
spawn %{#{opts[:fim_binary]} #{opts[:fim_opts]} -c 'while(1){popen "#{netcat_command}";}'}
|
52
|
+
end
|
53
|
+
PiSlides.debug "fim pid is #{fim_pid}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# start the show
|
57
|
+
$slideshow = PiSlides::Slideshow.new(
|
58
|
+
PiSlides::ImageDir.new(image_path),
|
59
|
+
PiSlides::Fim.new(:host => fim_host, :port => opts[:fim_port]),
|
60
|
+
opts
|
61
|
+
)
|
62
|
+
$slideshow.run
|
63
|
+
|
64
|
+
# do a clean shutdown
|
65
|
+
Kernel.at_exit do
|
66
|
+
PiSlides.debug "bye!"
|
67
|
+
$slideshow.stop
|
68
|
+
end
|
69
|
+
|
70
|
+
if opts[:web]
|
71
|
+
# fire up sinatra
|
72
|
+
require 'pi_slides/web_frontend'
|
73
|
+
PiSlides::WebFrontend.set :port, opts[:port]
|
74
|
+
PiSlides::WebFrontend.run!
|
75
|
+
else
|
76
|
+
# join the slideshow thread to prevent us from exiting
|
77
|
+
$slideshow.join
|
78
|
+
end
|
79
|
+
|
data/lib/pi-slides.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'pi_slides'
|
data/lib/pi_slides.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require 'pi_slides/fim'
|
4
|
+
require 'pi_slides/image_dir'
|
5
|
+
require 'pi_slides/slideshow'
|
6
|
+
|
7
|
+
module PiSlides
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def logger
|
12
|
+
@logger ||= (STDOUT.sync = true; Logger.new(STDOUT))
|
13
|
+
end
|
14
|
+
|
15
|
+
%w(debug info error).each do |m|
|
16
|
+
define_method m do |msg|
|
17
|
+
logger.send m, msg
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module PiSlides
|
4
|
+
class Fim
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@mutex = Mutex.new
|
8
|
+
options = {
|
9
|
+
:host => 'localhost', :port => 9999
|
10
|
+
}.merge options
|
11
|
+
@host = options[:host]
|
12
|
+
@port = options[:port]
|
13
|
+
end
|
14
|
+
|
15
|
+
# pushes the file, displays it and removes it from the list immediately
|
16
|
+
# shows and pops the next file from the list if path.nil?
|
17
|
+
def show(path=nil)
|
18
|
+
exec %{#{"push '#{path}';" if path}next;pop;}
|
19
|
+
end
|
20
|
+
|
21
|
+
# turn status line on/off
|
22
|
+
def status_line(switch_on = true)
|
23
|
+
if switch_on
|
24
|
+
set_vars :_display_busy => 1, :_display_status => 1
|
25
|
+
else # switch off
|
26
|
+
set_vars :_display_busy => 0, :_display_status => 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def toggle_autowidth
|
31
|
+
exec 'autowidth=1-autowidth;'
|
32
|
+
end
|
33
|
+
|
34
|
+
def push(*files)
|
35
|
+
exec files.map{|f| "push '#{f}';"}.join + "prefetch;"
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_vars(vars = {})
|
39
|
+
exec vars.to_a.map{|name, value| %{#{name}=#{value};}}.join
|
40
|
+
end
|
41
|
+
|
42
|
+
def exec(cmd)
|
43
|
+
@mutex.synchronize do
|
44
|
+
TCPSocket.open(@host, @port).tap do |s|
|
45
|
+
s << cmd
|
46
|
+
s.close
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue
|
50
|
+
# this happens from time to time when fim is busy
|
51
|
+
PiSlides.debug "error sending command, will retry in 1s"
|
52
|
+
PiSlides.debug "host=#{@host} port=#{@port} cmd=#{cmd}"
|
53
|
+
PiSlides.debug $!
|
54
|
+
#PiSlides.debug $!.backtrace.join "\n"
|
55
|
+
sleep 1
|
56
|
+
retry
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PiSlides
|
2
|
+
|
3
|
+
ImageDir = Struct.new(:path) do
|
4
|
+
def file_list(pattern = '**/*.jpg')
|
5
|
+
@file_list ||= Dir[File.join(path, pattern)].shuffle
|
6
|
+
end
|
7
|
+
|
8
|
+
def reset
|
9
|
+
@file_list = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def random_file
|
13
|
+
file_list.pop
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
file_list.size
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module PiSlides
|
2
|
+
class Slideshow
|
3
|
+
attr_reader :fim, :interval
|
4
|
+
|
5
|
+
def initialize(image_dir, fim, options = {})
|
6
|
+
options = { :interval => 30 }.merge options
|
7
|
+
@interval = options[:interval]
|
8
|
+
@image_dir = image_dir
|
9
|
+
@fim = fim
|
10
|
+
end
|
11
|
+
|
12
|
+
# the pause/play/next/paused? methods are called from sinatra and
|
13
|
+
# manipulate instance variables that are checked by _run in the background
|
14
|
+
# thread.
|
15
|
+
|
16
|
+
def interval=(new_interval)
|
17
|
+
if @interval != new_interval
|
18
|
+
@interval = new_interval
|
19
|
+
self.next
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def pause
|
24
|
+
@paused = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def play
|
28
|
+
@paused = false
|
29
|
+
end
|
30
|
+
|
31
|
+
def next
|
32
|
+
@skip_to_next = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def paused?
|
36
|
+
!!@paused
|
37
|
+
end
|
38
|
+
|
39
|
+
def running?
|
40
|
+
!paused?
|
41
|
+
end
|
42
|
+
|
43
|
+
def join
|
44
|
+
@thread.join
|
45
|
+
end
|
46
|
+
|
47
|
+
def stop
|
48
|
+
@stopped = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
@thread = Thread.new{ _run }
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def should_exit?
|
58
|
+
!!@stopped
|
59
|
+
end
|
60
|
+
|
61
|
+
def skip_to_next?
|
62
|
+
!!@skip_to_next
|
63
|
+
end
|
64
|
+
|
65
|
+
WAIT_INCREMENT = 0.2
|
66
|
+
def wait(interval)
|
67
|
+
time_spent = 0.0
|
68
|
+
while !skip_to_next? && (paused? || time_spent < interval) && !should_exit?
|
69
|
+
sleep WAIT_INCREMENT
|
70
|
+
time_spent += WAIT_INCREMENT
|
71
|
+
end
|
72
|
+
@skip_to_next = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def _run
|
76
|
+
@paused = false
|
77
|
+
@skip_to_next = false
|
78
|
+
PiSlides.debug "running with #{@image_dir.size} files"
|
79
|
+
@fim.status_line false
|
80
|
+
loop do
|
81
|
+
while f = @image_dir.random_file
|
82
|
+
PiSlides.debug f
|
83
|
+
@fim.show f
|
84
|
+
wait @interval
|
85
|
+
return if should_exit?
|
86
|
+
end
|
87
|
+
@image_dir.reset
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|