pi-slideshow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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