pi-slideshow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|