SwedishTV 0.0.2
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.
- data/README +12 -0
- data/bin/tv +12 -0
- data/lib/swe_tv.rb +844 -0
- metadata +45 -0
data/README
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
= SwedishTV
|
|
2
|
+
|
|
3
|
+
This gem presents a comprehensive web view of the programs running on the
|
|
4
|
+
Swedish TV channels. Run it as:
|
|
5
|
+
|
|
6
|
+
tv [port]
|
|
7
|
+
|
|
8
|
+
This starts a web server on the specified port (default is 9901). Connect
|
|
9
|
+
to the web server to see the program tables.
|
|
10
|
+
|
|
11
|
+
Using the web interface you can select which channels you want to show
|
|
12
|
+
and highlight the programs you are interested in.
|
data/bin/tv
ADDED
data/lib/swe_tv.rb
ADDED
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
require 'webrick'
|
|
2
|
+
include WEBrick
|
|
3
|
+
|
|
4
|
+
#puts "Content-type: text/plain"
|
|
5
|
+
#puts
|
|
6
|
+
|
|
7
|
+
# ----------------------------------------------------------------------
|
|
8
|
+
# SERVER PART
|
|
9
|
+
# ----------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
def start_webrick(config = {})
|
|
12
|
+
server = HTTPServer.new(config)
|
|
13
|
+
yield server if block_given?
|
|
14
|
+
%w(INT TERM).each {|signal| trap(signal) {server.shutdown}}
|
|
15
|
+
server.start
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start_tv_server(argv)
|
|
19
|
+
port = (argv[0] or 9901).to_i
|
|
20
|
+
|
|
21
|
+
config = {}
|
|
22
|
+
config[:Port] = port
|
|
23
|
+
|
|
24
|
+
start_webrick(config) {|server|
|
|
25
|
+
server.mount_proc("/tv") {|req, res|
|
|
26
|
+
res['content-type'] = 'text/html'
|
|
27
|
+
res['pragma'] = 'no-cache'
|
|
28
|
+
|
|
29
|
+
tv_app(req, res)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
server.mount_proc("/") { |req, res|
|
|
33
|
+
res['content-type'] = 'text/html'
|
|
34
|
+
res.body = <<INDEX_END
|
|
35
|
+
<frameset rows=*,20%>
|
|
36
|
+
<frame src="tv" name=table>
|
|
37
|
+
<frame src="tv?program=2" name=info>
|
|
38
|
+
</frameset>
|
|
39
|
+
INDEX_END
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# ----------------------------------------------------------------------
|
|
45
|
+
# APP PART
|
|
46
|
+
# ----------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
require 'net/http'
|
|
49
|
+
require 'FileUtils'
|
|
50
|
+
require 'tmpdir'
|
|
51
|
+
require 'cgi'
|
|
52
|
+
|
|
53
|
+
$debug = false
|
|
54
|
+
|
|
55
|
+
USERFILE = File.expand_path(File.join(ENV['HOME'], "/.swe_tv"))
|
|
56
|
+
|
|
57
|
+
CACHEDIR = File.join(Dir.tmpdir, "swe_tv_cache")
|
|
58
|
+
FileUtils::mkdir(CACHEDIR) unless File.exist?(CACHEDIR)
|
|
59
|
+
|
|
60
|
+
CHANNELS = {
|
|
61
|
+
'SVT1' =>21,
|
|
62
|
+
'SVT2' =>22,
|
|
63
|
+
'TV3' =>26,
|
|
64
|
+
'TV4' =>27,
|
|
65
|
+
'Kanal 5' =>10,
|
|
66
|
+
'ZTV' =>42,
|
|
67
|
+
'Via Act' => 28,
|
|
68
|
+
'TV8' => 43,
|
|
69
|
+
'TV 1000' => 33,
|
|
70
|
+
'Cinema' => 34,
|
|
71
|
+
'Canal+' => 11,
|
|
72
|
+
'C+ Gul' =>12,
|
|
73
|
+
'C+ Bla' =>55,
|
|
74
|
+
'C+ Zap' =>140,
|
|
75
|
+
'MTV' =>14,
|
|
76
|
+
'MTV2' =>74,
|
|
77
|
+
'Eurospo' =>9,
|
|
78
|
+
'Via Spo' =>53,
|
|
79
|
+
'Extreme' =>67,
|
|
80
|
+
'CNN' => 5,
|
|
81
|
+
'Eursp N' =>141,
|
|
82
|
+
'Sky New' =>18,
|
|
83
|
+
'CNBC' =>56,
|
|
84
|
+
'NBC Eur' =>93,
|
|
85
|
+
'24' =>101,
|
|
86
|
+
'SVT Eur' =>100,
|
|
87
|
+
'TV4 Plu' =>142,
|
|
88
|
+
'Barnkan' =>143,
|
|
89
|
+
'ORT Int' => 139,
|
|
90
|
+
'Discove' => 7,
|
|
91
|
+
'Dis Civ' => 118,
|
|
92
|
+
'Dis Sci' => 119,
|
|
93
|
+
'Dis T&A' => 120,
|
|
94
|
+
'Dis Mix' => 89,
|
|
95
|
+
'Anim Pl' => 54,
|
|
96
|
+
'Via Exp' =>76,
|
|
97
|
+
'Travel' =>25,
|
|
98
|
+
'Avante' =>68,
|
|
99
|
+
'Nat Geo' =>48,
|
|
100
|
+
'Cart Ne' =>4,
|
|
101
|
+
'Fox Kid' =>60,
|
|
102
|
+
'Nickelo' =>45,
|
|
103
|
+
'Disney' =>147,
|
|
104
|
+
'TCM' =>24,
|
|
105
|
+
'The Stu' =>70,
|
|
106
|
+
'Hallmar' =>13,
|
|
107
|
+
'VH-1' =>47,
|
|
108
|
+
'E!' =>114,
|
|
109
|
+
'Style' =>49,
|
|
110
|
+
'Fashion' =>82,
|
|
111
|
+
'Club' =>115,
|
|
112
|
+
'Reality' =>117,
|
|
113
|
+
'God Cha' =>111,
|
|
114
|
+
'Pink Pl' =>109,
|
|
115
|
+
'Playboy' =>110,
|
|
116
|
+
'Euronew' =>104,
|
|
117
|
+
'HRT1' =>50,
|
|
118
|
+
'TV Fin' =>29,
|
|
119
|
+
'YLE TV1' =>31,
|
|
120
|
+
'YLE TV2' =>36,
|
|
121
|
+
'TV3 Fin' =>39,
|
|
122
|
+
'TV Nor' =>73,
|
|
123
|
+
'NRK1' =>72,
|
|
124
|
+
'NRK2' =>63,
|
|
125
|
+
'TV2 Nor' =>37,
|
|
126
|
+
'TV3 Nor' =>62,
|
|
127
|
+
'ZTV Nor' =>75,
|
|
128
|
+
'TV Dan1' =>103,
|
|
129
|
+
'TV Dan2' =>105,
|
|
130
|
+
'DR1' =>58,
|
|
131
|
+
'DR2' =>59,
|
|
132
|
+
'TV2 Dan' =>71,
|
|
133
|
+
'TV3 Dan' =>57,
|
|
134
|
+
'TV2 Zul' =>126,
|
|
135
|
+
'3+ Dan' =>83,
|
|
136
|
+
'BBC Pri' =>2,
|
|
137
|
+
'BBC Wor' => 3,
|
|
138
|
+
'MBC' => 88,
|
|
139
|
+
'MDR' => 91,
|
|
140
|
+
'NDR' => 92,
|
|
141
|
+
'Viva' =>106,
|
|
142
|
+
'Vox' =>107,
|
|
143
|
+
'WDR' => 108,
|
|
144
|
+
'Kabel 1' =>113,
|
|
145
|
+
'Arte' =>84,
|
|
146
|
+
'ORB' => 94,
|
|
147
|
+
'PRO 7' => 98,
|
|
148
|
+
'Sudwest' =>99,
|
|
149
|
+
'Bayeris' =>81,
|
|
150
|
+
'3SAT' =>1,
|
|
151
|
+
'ZDF' =>51,
|
|
152
|
+
'ARD' =>69,
|
|
153
|
+
'Deut We' =>6,
|
|
154
|
+
'SAT1' =>17,
|
|
155
|
+
'DSF' =>8,
|
|
156
|
+
'RTL 2' =>15,
|
|
157
|
+
'RTL' =>16,
|
|
158
|
+
'Viva Pl' =>85,
|
|
159
|
+
'TV Polo' =>66,
|
|
160
|
+
'TV Chil' =>52,
|
|
161
|
+
'TV5' =>40,
|
|
162
|
+
'Mezzo' =>46,
|
|
163
|
+
'Fran 5' =>86,
|
|
164
|
+
'Rai Uno' =>64,
|
|
165
|
+
'Rai Due' =>65,
|
|
166
|
+
'TVE' =>41,
|
|
167
|
+
'DTU7' =>80,
|
|
168
|
+
'The Adu' =>87
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
def try_replace
|
|
172
|
+
begin
|
|
173
|
+
yield
|
|
174
|
+
rescue IndexError
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Fetch pages
|
|
179
|
+
class Fetch
|
|
180
|
+
def initialize(proxy = nil, proxyport = nil)
|
|
181
|
+
@proxy, @proxyport = proxy, proxyport
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Parse a network address into (host, port, path)
|
|
185
|
+
def parse_address(address, default_port = 80, default_path = '/')
|
|
186
|
+
address[/^http:\/\//] = ''
|
|
187
|
+
(host, path) = address.split('/', 2)
|
|
188
|
+
if path then path = '/' + path end
|
|
189
|
+
(host, port) = host.split(':', 2)
|
|
190
|
+
path = path || default_path
|
|
191
|
+
port = port || default_port
|
|
192
|
+
return [host, port, path]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Download page at address
|
|
196
|
+
def get(address)
|
|
197
|
+
(host, port, path) = parse_address(address)
|
|
198
|
+
puts "getting #{host} #{port} #{path}" if $debug
|
|
199
|
+
Net::HTTP.version_1_1
|
|
200
|
+
begin
|
|
201
|
+
Net::HTTP.start(host, port.to_i, @proxy, @proxyport.to_i) do |http|
|
|
202
|
+
resp, body = http.get(path)
|
|
203
|
+
return body
|
|
204
|
+
end
|
|
205
|
+
rescue Exception
|
|
206
|
+
raise "Page unavailable: <a href='http://#{address}'>http://#{address}</a> (#{$!})"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Keep track of time of programs
|
|
212
|
+
class ProgramTime
|
|
213
|
+
include Comparable
|
|
214
|
+
|
|
215
|
+
attr_reader :h, :m, :real
|
|
216
|
+
|
|
217
|
+
# 0-5 are considered night 5-0 morning
|
|
218
|
+
WRAPHOUR = 5
|
|
219
|
+
|
|
220
|
+
def ProgramTime.parse(s)
|
|
221
|
+
return ProgramTime.new(*(s.split(':').collect{|x| x.to_i}))
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def initialize(h=0,m=0)
|
|
225
|
+
@h, @m = h, m
|
|
226
|
+
@real = to_s
|
|
227
|
+
@m = (@m/10)*10
|
|
228
|
+
if @h < WRAPHOUR
|
|
229
|
+
@h += 24
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def -(time)
|
|
234
|
+
(@h - time.h - 1)*60 + (60 + @m - time.m)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def +(minutes)
|
|
238
|
+
h = @h
|
|
239
|
+
m = @m + minutes
|
|
240
|
+
h += m/60
|
|
241
|
+
m = m%60
|
|
242
|
+
return ProgramTime.new(h,m)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def to_s
|
|
246
|
+
sprintf("%02i:%02i", @h%24, m)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def <=>(time)
|
|
250
|
+
return -1 if @h < time.h
|
|
251
|
+
return 1 if @h > time.h
|
|
252
|
+
return -1 if @m < time.m
|
|
253
|
+
return 1 if @m > time.m
|
|
254
|
+
0
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def ProgramTime.now
|
|
258
|
+
t = Time.now
|
|
259
|
+
new(t.hour, t.min)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Represents user preferences for display
|
|
264
|
+
class Preferences
|
|
265
|
+
STYLES = %w(regular yellow grey red green blue purple cyan)
|
|
266
|
+
attr_accessor :channels
|
|
267
|
+
|
|
268
|
+
def initialize
|
|
269
|
+
@style = {}
|
|
270
|
+
@channels = ['SVT1', 'SVT2', 'TV3', 'TV4', 'Kanal 5', 'ZTV']
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def style(program)
|
|
274
|
+
@style[program.bare_name] || "regular"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def set_style(program, style)
|
|
278
|
+
if style == "regular"
|
|
279
|
+
@style.delete(program.bare_name)
|
|
280
|
+
else
|
|
281
|
+
@style[program.bare_name] = style
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Represents a program
|
|
287
|
+
class Program
|
|
288
|
+
attr_reader :start, :name, :desc, :showview, :id, :channel
|
|
289
|
+
attr_accessor :stop
|
|
290
|
+
|
|
291
|
+
FEATURELEN = 90
|
|
292
|
+
FEATURESTART = ProgramTime.new(20)
|
|
293
|
+
|
|
294
|
+
def Program.new_id
|
|
295
|
+
@id = 0 unless @id
|
|
296
|
+
@id = @id + 1
|
|
297
|
+
@id
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def initialize(channel, start, name, desc, showview, stop=nil)
|
|
301
|
+
@channel = channel
|
|
302
|
+
@start = start
|
|
303
|
+
@name = name; @desc = desc; @showview = showview
|
|
304
|
+
@stop = stop
|
|
305
|
+
@id = CHANNELS[channel].to_s + '_' + Program.new_id.to_s
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def duration; @stop - @start; end
|
|
309
|
+
|
|
310
|
+
def feature
|
|
311
|
+
@desc && @desc[/Regi:/]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def bare_name
|
|
315
|
+
bn = name.dup
|
|
316
|
+
try_replace {bn[/\(R\)/] = ''}
|
|
317
|
+
try_replace {bn[/,\s*forts/] = ''}
|
|
318
|
+
bn = $1 if bn[/(.*:)/]
|
|
319
|
+
bn.strip
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def original_name
|
|
323
|
+
@desc =~ /.*\((.*)\)/ ? $1 : bare_name
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# If this is a continuation of a program, returns that programs name
|
|
327
|
+
# otherwise nil.
|
|
328
|
+
def cont
|
|
329
|
+
return nil unless @name[/, forts$/]
|
|
330
|
+
cn = @name.dup
|
|
331
|
+
try_replace {cn[/, forts$/] = ''}
|
|
332
|
+
cn
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Represents a channel, a number of programs shown on a particular day. Also
|
|
337
|
+
# knows how to download the data for a channel.
|
|
338
|
+
class Channel
|
|
339
|
+
include Enumerable
|
|
340
|
+
attr_reader :channel
|
|
341
|
+
|
|
342
|
+
def Channel.new(day, channel, starttime, stoptime, flush = nil)
|
|
343
|
+
cache = "#{CACHEDIR}/#{channel}_" + day.strftime("%y%m%d")
|
|
344
|
+
if File.exists?(cache) && !flush
|
|
345
|
+
File.open(cache) {|f| return Marshal.load(f)}
|
|
346
|
+
else
|
|
347
|
+
chan = super(day, channel, starttime, stoptime)
|
|
348
|
+
File.open(cache, "w") {|f| Marshal.dump(chan, f)}
|
|
349
|
+
return chan
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def initialize(day, channel, starttime, stoptime)
|
|
354
|
+
@day = day
|
|
355
|
+
@channel = channel
|
|
356
|
+
@channum = CHANNELS[@channel]
|
|
357
|
+
@programs = []
|
|
358
|
+
@starttime = starttime
|
|
359
|
+
@stoptime = stoptime
|
|
360
|
+
parse_table(get_table)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def each(*args, &pr)
|
|
364
|
+
@programs.each(*args, &pr)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def get_table
|
|
368
|
+
page = "http://www.dagenstv.com/se/chart/?cha=" + \
|
|
369
|
+
"#{@channum}&dat=" + @day.strftime("%Y-%m-%d")
|
|
370
|
+
Fetch.new.get(page)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def parse_table(tbl)
|
|
374
|
+
raise "Could not find table" unless tbl
|
|
375
|
+
last = nil
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# Clear comments
|
|
379
|
+
tbl.gsub!(/<!--.*?-->/m, '')
|
|
380
|
+
|
|
381
|
+
# Match table rows
|
|
382
|
+
tbl.scan(/<tr>.*?(?=<tr>|<\/tr>)/m) do |row|
|
|
383
|
+
cellno = 0
|
|
384
|
+
start = name = desc = show = nil
|
|
385
|
+
row.scan(/<td[^>]*>.*?<\/td>/m) do |cell|
|
|
386
|
+
case cellno
|
|
387
|
+
when 0
|
|
388
|
+
start = cell[/[0-9]+:[0-9]+/]
|
|
389
|
+
when 2
|
|
390
|
+
if cell[/<span[^>]*class="stylecharteventname">(.*?)</m]
|
|
391
|
+
name = $1.strip
|
|
392
|
+
end
|
|
393
|
+
if cell[/<span[^>]*class="stylechartdescription">(.*?)<\/span>/m]
|
|
394
|
+
desc = $1.strip
|
|
395
|
+
end
|
|
396
|
+
if cell[/<span[^>]*class="stylechartshowview">(.*?)</m]
|
|
397
|
+
show = $1.strip
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
cellno += 1
|
|
401
|
+
end
|
|
402
|
+
if start && name
|
|
403
|
+
desc = desc || ""
|
|
404
|
+
start = ProgramTime.parse(start)
|
|
405
|
+
if start.between?(@starttime,@stoptime)
|
|
406
|
+
p = Program.new(channel, start, name, desc, show)
|
|
407
|
+
if last && last.start == start
|
|
408
|
+
@programs.pop
|
|
409
|
+
end
|
|
410
|
+
@programs << p
|
|
411
|
+
last.stop = start if last
|
|
412
|
+
last = p
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end # tbl scan
|
|
416
|
+
|
|
417
|
+
if @programs.length > 0
|
|
418
|
+
# If first program does not start at starttime insert pseudoprogram
|
|
419
|
+
if @programs[0].start != @starttime
|
|
420
|
+
@programs.unshift Program.new(channel, @starttime, nil, nil, nil,
|
|
421
|
+
@programs[0].start)
|
|
422
|
+
end
|
|
423
|
+
lastend = @programs[-1].start + 60
|
|
424
|
+
if lastend >= @stoptime
|
|
425
|
+
@programs[-1].stop = @stoptime
|
|
426
|
+
else
|
|
427
|
+
@programs[-1].stop = lastend
|
|
428
|
+
@programs.push Program.new(channel, lastend, nil, nil, nil, @stoptime)
|
|
429
|
+
end
|
|
430
|
+
else
|
|
431
|
+
@programs << Program.new(channel, @starttime, nil, nil, nil,
|
|
432
|
+
@stoptime)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def to_s
|
|
437
|
+
s = @channel + "\n----------------------------------------\n\n" + \
|
|
438
|
+
@programs.collect! {|p| p.to_s}.join("\n\n")
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# If this is a cont program returns the original program
|
|
442
|
+
def cont(program)
|
|
443
|
+
cn = program.cont
|
|
444
|
+
return nil unless cn
|
|
445
|
+
@programs.each {|p| return p if p.name == cn}
|
|
446
|
+
return nil
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
class Table
|
|
451
|
+
include Enumerable
|
|
452
|
+
|
|
453
|
+
attr_reader :day
|
|
454
|
+
|
|
455
|
+
START = ProgramTime.new(7)
|
|
456
|
+
STOP = ProgramTime.new(4)
|
|
457
|
+
|
|
458
|
+
def initialize(day, flush = nil, channels = CHANNELS.keys)
|
|
459
|
+
@day = day
|
|
460
|
+
@channels = channels.collect {|c| Channel.new(day, c, START, STOP, flush)}
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def each(*args, &pr)
|
|
464
|
+
@channels.each(*args, &pr)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def start; START; end
|
|
468
|
+
def stop; STOP; end
|
|
469
|
+
|
|
470
|
+
def program_from_id(id)
|
|
471
|
+
program = nil
|
|
472
|
+
each {|c|
|
|
473
|
+
c.each{|p|
|
|
474
|
+
program = p if p.id == id
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return program
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
class TableWriter
|
|
482
|
+
def initialize(table, prefs, id = nil)
|
|
483
|
+
@table, @prefs = table, prefs
|
|
484
|
+
@day = @table.day
|
|
485
|
+
@id = id
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def write(out)
|
|
489
|
+
tomorrow = @day + 3600*24
|
|
490
|
+
yesterday = @day - 3600*24
|
|
491
|
+
scrollto = '#' + (if @id then @id.to_s else 'now' end)
|
|
492
|
+
out << <<-STOP
|
|
493
|
+
<html>
|
|
494
|
+
<head>
|
|
495
|
+
<title>Dagens TV-program</title>
|
|
496
|
+
<style>
|
|
497
|
+
td {font-size: 8pt; font-family: Verdana;}
|
|
498
|
+
.time {font-size: 7pt;}
|
|
499
|
+
.timeeven {font-size: 7pt; background: #cfc;}
|
|
500
|
+
.timenow {font-size: 7pt; background: #060; color: #fff;}
|
|
501
|
+
|
|
502
|
+
.regular {background: #fff;}
|
|
503
|
+
.yellow {background: #ffc;}
|
|
504
|
+
.grey {background: #ccc;}
|
|
505
|
+
.red {background: #fcc;}
|
|
506
|
+
.green {background: #cfc;}
|
|
507
|
+
.blue {background: #ccf;}
|
|
508
|
+
.purple {background: #fcf;}
|
|
509
|
+
.cyan {background: #cff;}
|
|
510
|
+
.feature {background: #fcc;}
|
|
511
|
+
|
|
512
|
+
.regularnow {background: #ada;}
|
|
513
|
+
.greynow {background: #9a9;}
|
|
514
|
+
.yellownow {background: #eeb;}
|
|
515
|
+
.rednow {background: #ebb;}
|
|
516
|
+
.greennow {background: #beb;}
|
|
517
|
+
.bluenow {background: #bbe;}
|
|
518
|
+
.purplenow {background: #ebe;}
|
|
519
|
+
.cyannow {background: #bee;}
|
|
520
|
+
.featurenow {background: #ebb;}
|
|
521
|
+
|
|
522
|
+
.programnow {font-size: 8pt; font-family: Verdana; background: #9c9;}
|
|
523
|
+
|
|
524
|
+
.head {font-size: 10pt; font-family: Verdana; font-weight: bold;
|
|
525
|
+
text-align: center}
|
|
526
|
+
h1 {text-align: center; color: #666633; font-family: arial}
|
|
527
|
+
a {color: #000000; text-decoration: none}
|
|
528
|
+
a.now {}
|
|
529
|
+
a.imdb {font-size: 7pt}
|
|
530
|
+
</style>
|
|
531
|
+
<script>
|
|
532
|
+
function showMessage(m) {
|
|
533
|
+
start = "<html> <head> <style> h1 {font-family: arial; font-size: 16; text-align: center} p.showview {color: red} </style> </head> <body bgcolor=white>"
|
|
534
|
+
stop = "</body></html>"
|
|
535
|
+
|
|
536
|
+
alert(start + m + stop)
|
|
537
|
+
}
|
|
538
|
+
</script>
|
|
539
|
+
<head>
|
|
540
|
+
<body bgcolor=white onload='self.location.href="#{scrollto}"'>
|
|
541
|
+
<h1>#{@day.strftime("%A %Y-%m-%d")}</h1>
|
|
542
|
+
<table width=100% align=center>
|
|
543
|
+
<tr>
|
|
544
|
+
<td>
|
|
545
|
+
<p align=left>
|
|
546
|
+
<a target=table href="tv?day=#{yesterday.strftime("%Y-%m-%d")}"><<
|
|
547
|
+
#{yesterday.strftime("%A %Y-%m-%d")}</a>
|
|
548
|
+
</p>
|
|
549
|
+
</td>
|
|
550
|
+
<td>
|
|
551
|
+
<p align=right>
|
|
552
|
+
<a target=table href="tv?day=#{tomorrow.strftime('%Y-%m-%d')}"
|
|
553
|
+
>#{tomorrow.strftime("%A %Y-%m-%d")} >></a>
|
|
554
|
+
</p>
|
|
555
|
+
</td>
|
|
556
|
+
</tr>
|
|
557
|
+
<table bgcolor=#cccccc align=center>
|
|
558
|
+
<tr><td><table cellspacing=1 cellpadding=3 align=center>
|
|
559
|
+
STOP
|
|
560
|
+
|
|
561
|
+
out << '<tr>'
|
|
562
|
+
@table.each_with_index {|c,i|
|
|
563
|
+
if i%2==0
|
|
564
|
+
out << "<td class=head bgcolor=#ffffff>Tid</td>"
|
|
565
|
+
end
|
|
566
|
+
out << "<td class=head bgcolor=#ffffff>#{c.channel}</td>"
|
|
567
|
+
}
|
|
568
|
+
out << "<td class=head bgcolor=#ffffff>Tid</td>"
|
|
569
|
+
out << '</tr>'
|
|
570
|
+
|
|
571
|
+
now = ProgramTime.now
|
|
572
|
+
time = @table.start
|
|
573
|
+
while time < @table.stop
|
|
574
|
+
out << '<tr>'
|
|
575
|
+
style = 'time'
|
|
576
|
+
style = 'timeeven' if time.m == 0
|
|
577
|
+
isnow = false
|
|
578
|
+
if (now - time) >= 0 && (now-time) < 10
|
|
579
|
+
style = 'timenow'
|
|
580
|
+
isnow = true
|
|
581
|
+
end
|
|
582
|
+
@table.each_with_index {|c,i|
|
|
583
|
+
if i==0 && isnow
|
|
584
|
+
out << "<td class=#{style} bgcolor=#ffffff><a name=now>#{time}</a></td>"
|
|
585
|
+
elsif i%2==0
|
|
586
|
+
out << "<td class=#{style} bgcolor=#ffffff>#{time}</td>"
|
|
587
|
+
end
|
|
588
|
+
channel_row(out, c, time)
|
|
589
|
+
}
|
|
590
|
+
out << "<td class=#{style} bgcolor=#ffffff>#{time}</td>"
|
|
591
|
+
out << "</tr>\n"
|
|
592
|
+
time = time + 10
|
|
593
|
+
end
|
|
594
|
+
select_row(out)
|
|
595
|
+
out << "</table></td></tr></table>\n"
|
|
596
|
+
out << "</body>\n</html>\n"
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def to_s
|
|
600
|
+
s = ''
|
|
601
|
+
write(s)
|
|
602
|
+
return s
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def channel_row(out, channel, time)
|
|
606
|
+
match = channel.detect {|p| p.start == time}
|
|
607
|
+
if match
|
|
608
|
+
program_row(out, channel, match)
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def select_row(out)
|
|
613
|
+
out << "<tr>"
|
|
614
|
+
channels = CHANNELS.keys.sort
|
|
615
|
+
channels << '--'
|
|
616
|
+
channels << 'Remove'
|
|
617
|
+
@prefs.channels.each_with_index {|c,i|
|
|
618
|
+
if i%2 == 0
|
|
619
|
+
out << <<-ADD
|
|
620
|
+
<td align=center>
|
|
621
|
+
</td>
|
|
622
|
+
ADD
|
|
623
|
+
end
|
|
624
|
+
out << <<-OPEN
|
|
625
|
+
<td align=center>
|
|
626
|
+
<form method=post>
|
|
627
|
+
<input type=hidden name=action value="select_channel">
|
|
628
|
+
<input type=hidden name=column value="#{i}">
|
|
629
|
+
<select name=channel onChange='submit()'>
|
|
630
|
+
OPEN
|
|
631
|
+
channels.each {|c2|
|
|
632
|
+
sel = c==c2 ? "selected" : ""
|
|
633
|
+
out << "<option #{sel} value='#{c2}'> #{c2}"
|
|
634
|
+
}
|
|
635
|
+
out << <<-CLOSE
|
|
636
|
+
</select>
|
|
637
|
+
</form>
|
|
638
|
+
</td>
|
|
639
|
+
CLOSE
|
|
640
|
+
}
|
|
641
|
+
out << <<-ADD
|
|
642
|
+
<td align=center>
|
|
643
|
+
<form method=post>
|
|
644
|
+
<input type=hidden name=action value="add_channel">
|
|
645
|
+
<input type=submit value='Add'>
|
|
646
|
+
</form>
|
|
647
|
+
</td>
|
|
648
|
+
</tr>
|
|
649
|
+
ADD
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def program_style(channel, program)
|
|
653
|
+
orig_program = program
|
|
654
|
+
program = channel.cont(program) || program
|
|
655
|
+
style = if @prefs.style(program) != "regular"
|
|
656
|
+
@prefs.style(program)
|
|
657
|
+
elsif program.feature
|
|
658
|
+
"feature"
|
|
659
|
+
else
|
|
660
|
+
"regular"
|
|
661
|
+
end
|
|
662
|
+
if program_now(orig_program)
|
|
663
|
+
style + "now"
|
|
664
|
+
else
|
|
665
|
+
style
|
|
666
|
+
end
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def program_now(program)
|
|
670
|
+
@now = @now || ProgramTime.now
|
|
671
|
+
@now >= program.start && @now < program.stop
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def program_link(channel, program)
|
|
675
|
+
program = channel.cont(program) || program
|
|
676
|
+
klass = program_now(program) ? "class=now" : ""
|
|
677
|
+
daystr = @day.strftime("%Y-%m-%d")
|
|
678
|
+
%Q{<a name=#{program.id} href="tv?program=#{program.id}&day=#{daystr}" title="#{program.showview}"
|
|
679
|
+
target=info #{klass}>#{program.name}</a>}
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def feature_link(channel, program)
|
|
683
|
+
program = channel.cont(program) || program
|
|
684
|
+
return "" unless program.feature
|
|
685
|
+
original_title = program.original_name
|
|
686
|
+
original_title = CGI.escape(original_title)
|
|
687
|
+
"<a class=imdb href='http://www.imdb.com/Find?#{original_title}'>(IMDB)</a>"
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def program_row(out, channel, program)
|
|
691
|
+
rows = program.duration/10
|
|
692
|
+
if program.name
|
|
693
|
+
out << %Q{
|
|
694
|
+
<td class=#{program_style(channel, program)} valign=top bgcolor=#ffffff
|
|
695
|
+
rowspan=#{rows}>
|
|
696
|
+
#{program_link(channel, program)} #{feature_link(channel, program)}
|
|
697
|
+
</td>
|
|
698
|
+
}
|
|
699
|
+
else
|
|
700
|
+
out << "<td rowspan=#{rows}></td>"
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
class ProgramWriter
|
|
706
|
+
def initialize(table, prefs, id)
|
|
707
|
+
@prefs = prefs
|
|
708
|
+
@program = table.program_from_id(id)
|
|
709
|
+
@day = table.day
|
|
710
|
+
@id = id
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def feature_link(program)
|
|
714
|
+
original_title = program.original_name
|
|
715
|
+
original_title_esc = CGI.escape(original_title)
|
|
716
|
+
"<a class=imdb href='http://www.imdb.com/Find?#{original_title_esc}'>IMDB: #{original_title}</a>"
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
def write(out)
|
|
720
|
+
unless @program
|
|
721
|
+
out << "<html></html>"
|
|
722
|
+
return
|
|
723
|
+
end
|
|
724
|
+
daystr = @day.strftime("%Y-%m-%d")
|
|
725
|
+
out << %Q{
|
|
726
|
+
<html><head>
|
|
727
|
+
<style>
|
|
728
|
+
p {font-size: 8pt; font-family: Verdana; margin: 0.3em}
|
|
729
|
+
body {font-size: 10}
|
|
730
|
+
h1 {font-family: arial; font-size: 12}
|
|
731
|
+
p.showview {color: red}
|
|
732
|
+
</style>
|
|
733
|
+
</head>
|
|
734
|
+
<body bgcolor=white>
|
|
735
|
+
<form target=table method=post>
|
|
736
|
+
<table width=100% cellspacing=10><tr>
|
|
737
|
+
<td valign=top width=50%>
|
|
738
|
+
<h1>#{@program.channel} #{@program.start.real} #{@program.name}
|
|
739
|
+
#{@program.showview}</h1>
|
|
740
|
+
<p>#{@program.desc}</p>
|
|
741
|
+
</td>
|
|
742
|
+
<td width=25% valign=top>
|
|
743
|
+
#{styles}
|
|
744
|
+
</td>
|
|
745
|
+
|
|
746
|
+
<td width=25%>
|
|
747
|
+
<input type=submit value="Update">
|
|
748
|
+
<input type=hidden name=action value=prefs>
|
|
749
|
+
<input type=hidden name=id value="#{@id}">
|
|
750
|
+
<input type=hidden name=day value="#{daystr}">
|
|
751
|
+
</td>
|
|
752
|
+
</form>
|
|
753
|
+
</body></html>
|
|
754
|
+
}
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def styles
|
|
758
|
+
s = "<table><tr><td valign=top>"
|
|
759
|
+
Preferences::STYLES.each_with_index {|sty, i|
|
|
760
|
+
checked = (@prefs.style(@program) == sty) ? 'checked' : ''
|
|
761
|
+
if i%4 == 0 && i!=0
|
|
762
|
+
s << "</td><td> </td><td valign=top>"
|
|
763
|
+
end
|
|
764
|
+
s << "<p><input type=radio name=style value=#{sty} #{checked} onclick='submit()'> #{sty}</p>"
|
|
765
|
+
}
|
|
766
|
+
s << "</td></tr></table>"
|
|
767
|
+
return s
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
def to_s; s=''; write(s); s; end
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
def tv_app(req, res)
|
|
774
|
+
today = Time.now
|
|
775
|
+
if today.hour <=4
|
|
776
|
+
today = today - 3600*24
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
query = req.query
|
|
780
|
+
|
|
781
|
+
if query['day']
|
|
782
|
+
day = query['day']
|
|
783
|
+
daydata = day.split('-').collect {|s| s.to_i}
|
|
784
|
+
if daydata.length == 3
|
|
785
|
+
daydata << 12
|
|
786
|
+
today = Time.mktime(*daydata)
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
headers = {}
|
|
791
|
+
|
|
792
|
+
prefs = nil
|
|
793
|
+
if File.exists?(USERFILE)
|
|
794
|
+
File.open(USERFILE) {|f| prefs = Marshal.load(f)}
|
|
795
|
+
else
|
|
796
|
+
prefs = Preferences.new
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
if prefs.channels == nil
|
|
800
|
+
prefs.channels = ['SVT1', 'SVT2', 'TV3', 'TV4', 'Kanal 5', 'ZTV']
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
if query['program']
|
|
804
|
+
table = Table.new(today, false, prefs.channels)
|
|
805
|
+
program = query['program']
|
|
806
|
+
ProgramWriter.new(table, prefs, program).write(res.body)
|
|
807
|
+
|
|
808
|
+
elsif query['action'] == 'prefs'
|
|
809
|
+
table = Table.new(today, false, prefs.channels)
|
|
810
|
+
id = query['id'].to_s
|
|
811
|
+
program = table.program_from_id(id)
|
|
812
|
+
style = query['style']
|
|
813
|
+
if program
|
|
814
|
+
prefs.set_style(program, style)
|
|
815
|
+
File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
|
|
816
|
+
end
|
|
817
|
+
TableWriter.new(table, prefs, id).write(res.body)
|
|
818
|
+
|
|
819
|
+
elsif query['action'] == 'select_channel'
|
|
820
|
+
column = query['column'].to_i
|
|
821
|
+
program = query['channel']
|
|
822
|
+
if program == 'Remove'
|
|
823
|
+
if prefs.channels.length > 1
|
|
824
|
+
prefs.channels.delete_at(column)
|
|
825
|
+
end
|
|
826
|
+
else
|
|
827
|
+
prefs.channels[column] = program
|
|
828
|
+
end
|
|
829
|
+
File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
|
|
830
|
+
table = Table.new(today, false, prefs.channels)
|
|
831
|
+
TableWriter.new(table, prefs).write(res.body)
|
|
832
|
+
|
|
833
|
+
elsif query['action'] == 'add_channel'
|
|
834
|
+
prefs.channels << 'SVT1'
|
|
835
|
+
File.open(USERFILE, "w") {|f| Marshal.dump(prefs, f)}
|
|
836
|
+
table = Table.new(today, false, prefs.channels)
|
|
837
|
+
TableWriter.new(table, prefs).write(res.body)
|
|
838
|
+
|
|
839
|
+
else
|
|
840
|
+
table = Table.new(today, false, prefs.channels)
|
|
841
|
+
TableWriter.new(table, prefs).write(res.body)
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
|
metadata
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
rubygems_version: 0.8.11
|
|
3
|
+
specification_version: 1
|
|
4
|
+
name: SwedishTV
|
|
5
|
+
version: !ruby/object:Gem::Version
|
|
6
|
+
version: 0.0.2
|
|
7
|
+
date: 2005-08-30 00:00:00 +02:00
|
|
8
|
+
summary: Shows Swedish TV channel schedules in web browser.
|
|
9
|
+
require_paths:
|
|
10
|
+
- lib
|
|
11
|
+
email: niklas@frykholm.se
|
|
12
|
+
homepage: http://www.frykholm.se/
|
|
13
|
+
rubyforge_project:
|
|
14
|
+
description:
|
|
15
|
+
autorequire:
|
|
16
|
+
default_executable:
|
|
17
|
+
bindir: bin
|
|
18
|
+
has_rdoc: true
|
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
-
|
|
22
|
+
- ">"
|
|
23
|
+
- !ruby/object:Gem::Version
|
|
24
|
+
version: 0.0.0
|
|
25
|
+
version:
|
|
26
|
+
platform: ruby
|
|
27
|
+
signing_key:
|
|
28
|
+
cert_chain:
|
|
29
|
+
authors:
|
|
30
|
+
- Niklas Frykholm
|
|
31
|
+
files:
|
|
32
|
+
- bin/CVS
|
|
33
|
+
- bin/tv
|
|
34
|
+
- lib/CVS
|
|
35
|
+
- lib/swe_tv.rb
|
|
36
|
+
- README
|
|
37
|
+
test_files: []
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
extra_rdoc_files:
|
|
40
|
+
- README
|
|
41
|
+
executables:
|
|
42
|
+
- tv
|
|
43
|
+
extensions: []
|
|
44
|
+
requirements: []
|
|
45
|
+
dependencies: []
|