aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md41
-rw-r--r--index.html.erb136
-rw-r--r--lib/events.rb28
-rw-r--r--lib/room-schedule.rb (renamed from room-schedule.rb)0
-rw-r--r--program.ics.erb17
-rwxr-xr-x[-rw-r--r--]ramaskrik-program.rb106
6 files changed, 293 insertions, 35 deletions
diff --git a/README.md b/README.md
index ed25d3c..e618284 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,47 @@ Et lite script jeg laget for å gjøre det enklere for meg selv å få oversikt
Siden som genereres av scriptet krever en nettleser med SVG 1.1 støtte, noe [de fleste](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject#Browser_compatibility) skulle ha i dag.
+## Bruksanvisning
+
+Programmet tar en `.json` fil med data som input. Dataene må være en json liste (array) hvor hvert object i listen er en visning. Hver visning må ha følgende felter:
+
+- title: tekst, Filmens tittel
+- venue: telst, Sal/auditorium/rom for visningen
+- start\_time: tid+dato i et noenlunde maskinlesbart standardformat (ISO8601 er fint!)
+- duration: Visningens lengde i sekunder
+- image: url til filmplakat (valgfritt)
+
+Her er et eksempel fra en fil:
+
+```json
+[
+ {
+ "title":"Satanic Hispanics",
+ "venue":"Storsalen",
+ "start_time":"2023-10-19 10:30:00 +0200",
+ "duration":"6300",
+ "image":"https://mff.dx.no/132216.jpeg?w=270&h=480&fit=crop&auto=compress"
+ },
+ {
+ ...
+ }
+]
+```
+
+Kjør programmet slik:
+
+```
+% ruby ramaskrik-program.rb <input-fil.json>
+```
+
+Den skal da generere en `index.html` og en `program.ics` (kalender) fil i samme katalog.
+
+Last opp disse til et egnet sted på en webserver, og du har ditt eget ramaskrik program.
+
+Programmet kan selvsagt brukes til å lage andre tilsvarende programoversikter også. Bare
+tilpass koden og malene etter ønske.
+
+
## LISENS
Copyright (C) 2018 [Harald Eilertsen](haraldei@anduin.net)
diff --git a/index.html.erb b/index.html.erb
index f1dfbf0..6b8630e 100644
--- a/index.html.erb
+++ b/index.html.erb
@@ -1,12 +1,138 @@
<!DOCTYPE html>
<html>
<head>
- <title>Ramaskrik 2018 programoversikt</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta charset="utf-8">
+ <title><%= title %></title>
+<style>
+.events {
+ display:flex;
+ justify-content: space-around;
+ width: 800px;
+}
+.event-column {
+ width: 350px;
+}
+.event-column-inner,
+.time-column-inner {
+ position: relative;
+}
+.timestamp {
+ position: absolute;
+ font-size: 10px;
+}
+.event-item {
+ display: flex;
+ position: absolute;
+ background-color: #b8afaf;
+ border: 1px solid #ccc;
+ padding: 0.3em;
+ color: #000;
+ box-sizing: border-box;
+ width: 100%;
+
+ font-family: Helvetica;
+ font-size: small;
+}
+.event-item.selected {
+ background-color: #709270;
+}
+.event-item-image {
+ float: left;
+ margin-right: 0.3em;
+}
+.event-item-image img {
+ width: 3em;
+ object-fit: cover;
+}
+</style>
</head>
<body>
- <h1>Ramaskrik 2018 programoversikt</h1>
- <% generate_graphs do |graph| %>
- <%= graph.burn_svg_only() %>
- <% end %>
+ <h1><%= title %></h1>
+ <section id="introduction">
+ <p>Dette er en annen visning av programmet for skrekkfilmfestivalen
+ <a href="https://ramaskrik.no">Ramaskrik 2023</a>. Jeg laget det for
+ meg selv, for å få en bedre oversikt over hvilke filmer som går hvor og når. Offisielt program finner du
+ på <a href="https://ramaskrik.no/program/">Ramaskrik sine egne websider</a>. Denne siden ble generert
+ <%= Date.today.strftime('%d.%m.%Y') %>, evt. endringer i programmet etter det er ikke tatt med.</p>
+
+ <p>Du kan merke hvilke filmer du ønsker å se ved å klikke/tappe på de. Siden vil huske hvilke filmer du
+ har valgt, så du vil kunne bruke denne siden til å planlegge ruten din igjennom festivalen hvis du vil.
+ (Ingen data sendes til websiden, hvilke filmer du har valgt vil kun lagres i din egen nettleser.)</p>
+
+ <p>Programmet er også tilgjengelig som en kalenderfil. <a href="./program-ramaskrik-2023.ics">Klikk her</a>
+ for å legge den til i kalenderen din</a>.</p>
+
+ <p>Har du spørsmål, ta kontakt med meg <a href="mailto:haraldei-ramaprogram@anduin.net">via epost</a>.</p>
+ </section>
+ <section id="events-list">
+ <% eventlist.each do |date, events| %>
+ <h2><%= date.strftime("%A %d.%m.%Y") %></h2>
+ <div class="events" style="height: <%= 200 + (events.end_time - events.start_time) / 50 %>px; width: <%= 100 + (events.venues.length * 350) %>px;">
+ <% start_time = events.start_time.to_i %>
+ <% end_time = events.end_time.to_i %>
+ <% events.venues.sort.each do |venue| %>
+ <div class="time-column" style="position: relative;">
+ <h3>Kl.</h3>
+ <div class="time-column-inner">
+ <% (start_time..end_time).step(3600 / 4) do |time| %>
+ <div class="timestamp" style="top: <%= (time - start_time).to_i / 50 - 5 %>px; height: 10px;">
+ <%= Time.at(time).strftime('%H:%M') %>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ <div class="event-column">
+ <h3><%= venue %></h2>
+ <div class="event-column-inner">
+ <% events.events.select{ |e| e.venue == venue }.each do |e| %>
+ <div id="<%= e.slug %>" class="event-item" style="top: <%= e.offset %>px; height: <%= e.height %>px;">
+ <div class="event-item-image">
+ <% if e.image_url %>
+ <img src="<%= e.image_url %>">
+ <% end %>
+ </div>
+ <div class="event-item-meta">
+ <div class="event-item-title"><%= e.title %></div>
+ <div class="event-item-times">
+ <time datetime="<%= e.start_time.iso8601 %>">
+ <%= e.start_time.strftime('%H:%M') %>
+ </time>
+ -
+ <time datetime="<%= (e.start_time + e.duration).iso8601 %>">
+ <%= (e.start_time + e.duration).strftime('%H:%M') %>
+ </time>
+ </div>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ <% end %>
+ </section>
+ <section id="footer">
+ <p>Denne siden ble generert den <%= DateTime.now.strftime('%d.%m.%Y kl. %H:%M') %> av
+ <a href="https://code.volse.net/ramaskrik-program.git/">Ramaskrik programoversikt-generator</a>
+ </section>
</body>
</html>
+<script>
+ let event_items = document.getElementsByClassName('event-item');
+ for (const element of event_items) {
+ if (localStorage.getItem(element.id) === 'selected') {
+ element.classList.add('selected');
+ }
+
+ element.addEventListener('click', event => {
+ const result = event.currentTarget.classList.toggle('selected');
+
+ if (result) {
+ localStorage.setItem(event.currentTarget.id, 'selected');
+ } else {
+ localStorage.removeItem(event.currentTarget.id);
+ }
+ });
+ }
+</script>
diff --git a/lib/events.rb b/lib/events.rb
new file mode 100644
index 0000000..e647930
--- /dev/null
+++ b/lib/events.rb
@@ -0,0 +1,28 @@
+require 'time'
+
+module Events
+ class Event
+
+ attr_reader :start_time
+ attr_reader :duration
+ attr_reader :title
+ attr_reader :venue
+ attr_reader :image_url
+
+ def initialize(attrs)
+ @start_time = Time.parse(attrs['start_time'])
+ @duration = attrs['duration'].to_i
+ @title = attrs['title']
+ @venue = attrs['venue']
+ @image_url = attrs['image'] unless attrs['image'].length < 10
+ end
+
+ def date
+ @start_time.to_date
+ end
+
+ def slug
+ "#{start_time.to_i}-#{title.downcase.gsub(/[^a-z0-9_-]/, '')}"
+ end
+ end
+end
diff --git a/room-schedule.rb b/lib/room-schedule.rb
index fd4e282..fd4e282 100644
--- a/room-schedule.rb
+++ b/lib/room-schedule.rb
diff --git a/program.ics.erb b/program.ics.erb
new file mode 100644
index 0000000..60c55bd
--- /dev/null
+++ b/program.ics.erb
@@ -0,0 +1,17 @@
+BEGIN:VCALENDAR
+PRODID:-//volse.net//NONSGML ramaskrik-program-generator//NB
+VERSION:2.0
+DTSTAMP;TZID=Europe/Oslo:<%= DateTime.now.strftime('%Y%m%dT%H%M%S') %>
+<% eventlist.each do |date, events| %>
+<% events.events.each do |e| %>
+BEGIN:VEVENT
+UUID:<%= e.slug %>
+DTSTAMP;TZID=Europe/Oslo:<%= DateTime.now.strftime('%Y%m%dT%H%M%S') %>
+DTSTART;TZID=Europe/Oslo:<%= e.start_time.strftime('%Y%m%dT%H%M%S') %>
+DURATION:PT<%= e.duration %>S
+SUMMARY:<%= e.title %>
+LOCATION:<%= e.venue %>
+END:VEVENT
+<% end %>
+<% end %>
+END:VCALENDAR
diff --git a/ramaskrik-program.rb b/ramaskrik-program.rb
index 3c56edc..5dfe5d5 100644..100755
--- a/ramaskrik-program.rb
+++ b/ramaskrik-program.rb
@@ -1,3 +1,5 @@
+#!/usr/bin/env ruby
+
# Ramaskrik Program Schedule plotter.
# Copyright (C) 2018 Harald Eilertsen <haraldei@anduin.net>
#
@@ -14,42 +16,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+$LOAD_PATH << File.join(File.dirname( __FILE__ ), "lib")
+
require 'date'
+require 'delegate'
require 'erb'
+require 'events'
+require 'json'
+require 'scrapers/ramaskrik'
require 'nokogiri'
require 'open-uri'
-require 'room-schedule'
+#require 'room-schedule'
require 'uri'
-class Movie
- attr_reader :title, :link, :start_time, :end_time, :venue
-
- def initialize(node)
- @title = node.css("h4").text.strip
- @link = URI.join("https://ramaskrik.no", node.css("h4 a").attribute('href').value)
- @start_time = DateTime.parse(node.css(".date").attribute('title').value)
- @end_time = DateTime.parse(@start_time.strftime('%F') + node.css(".prgtype-endTime").text.strip.sub(/\w+/, ''))
- @end_time += 1 if @end_time < @start_time
- @venue = node.css(".place").text.strip
- end
-
- def to_s
- "#{title} #{start_time} - #{end_time}, #{venue}"
- end
-end
-
-def scrape_program
- doc = Nokogiri::HTML(open("https://ramaskrik.no/program/"))
- doc.css(".kultur-type-movie").map { |movie| Movie.new(movie) }
-end
-
-def generate_graphs
- days = scrape_program.group_by { |movie| movie.start_time.strftime("%A %d.%m.%Y") }
- days.map do |d, movies|
- rooms = movies.group_by { |m| m.venue }.delete_if { |t, _| t == "Ramaskrik" }
+def generate_graphs(events)
+ days = events.group_by { |event| event.start_time.strftime("%A %d.%m.%Y") }
+ days.map do |day, events|
+ rooms = events.group_by { |e| e.venue }
graph = SVG::Graph::RoomSchedule.new({
- graph_title: d,
+ graph_title: day,
show_graph_title: true,
show_x_guidelines: true,
width: 1280,
@@ -67,6 +53,66 @@ def generate_graphs
end
end
-t = ERB.new(IO.read("index.html.erb"))
-IO.write("index.html", t.result(binding))
+class EventDecorator < SimpleDelegator
+ attr_reader :offset
+ attr_reader :height
+
+ def calc_offsets(time_offset)
+ @offset = (start_time - time_offset).to_i / 50
+ @height = duration / 50;
+ start_time + duration
+ end
+end
+
+class SortedEventList
+ attr_reader :venues
+ attr_reader :events
+ attr_reader :start_time
+ attr_reader :end_time
+
+ def initialize(events)
+ @venues = events.map{ |e| e.venue }.uniq
+
+ @events = events
+ .sort{ |a,b| a.start_time - b.start_time }
+ .map{ |e| EventDecorator.new(e) }
+
+ @start_time = @events.first.start_time - (@events.first.start_time.min * 60)
+
+ @events.group_by{|e| e.venue}.each do |venue, events|
+ time_offset = start_time
+ time_end = start_time
+ events.each do |event|
+ time_end = event.calc_offsets(time_offset)
+ end
+
+ @end_time = time_end if @end_time.nil? || @end_time < time_end
+ end
+ end
+end
+
+def import_events_from_json(input)
+ JSON
+ .parse(input)
+ .map{ |obj| Events::Event.new(obj) }
+ .group_by{ |e| e.date }
+end
+
+def make_sorted_event_lists_by_date(events_by_date)
+ new_list = {}
+ events_by_date.each do |date, events|
+ new_list[date] = SortedEventList.new(events)
+ end
+
+ new_list
+end
+
+title = "Ramaskrik 2023 - Program"
+
+eventlist = make_sorted_event_lists_by_date(import_events_from_json(IO.read(ARGV[0])))
+
+t_html = ERB.new(IO.read("index.html.erb"), trim_mode: '>')
+IO.write("index.html", t_html.result(binding))
+t_ics = ERB.new(IO.read("program.ics.erb"), trim_mode: '<>')
+IO.write("program.ics", t_ics.result(binding).gsub(/\n/, "\r\n"))