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.
@@ -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
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
+
@@ -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
+
@@ -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
+
@@ -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
+
@@ -0,0 +1 @@
1
+ require 'pi_slides'
@@ -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