imdb-terminal 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +60 -0
  3. data/bin/imdb +787 -0
  4. metadata +69 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 668a4e977795d0075d70382890a72e911688c452d67cb02ba432f1cfefdacd01
4
+ data.tar.gz: 4d1ac685489c8b509349841fc55e00285576ed9be0815802740731e5762cb954
5
+ SHA512:
6
+ metadata.gz: 627fbd556497607248a0ebb78012d0ce9b5577eab31a0b8f8446c0b65b1aaba6bb78cc25216d0c115db672a6b179e21b468f86727ce09a2f397d98a0a8ceb893
7
+ data.tar.gz: 87531c704b0b7b1d5bea5d2a8bd91b656f08a6ded7d4639c2441ca3923a6c282920bdbe74c430a70615e117e58c272fa468f7f27bda0a7c1735ec5f8e4a6fbb8
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+
2
+ ![Ruby](https://img.shields.io/badge/language-Ruby-red) [![Gem Version](https://badge.fury.io/rb/imdb-terminal.svg)](https://badge.fury.io/rb/imdb-terminal) ![Unlicense](https://img.shields.io/badge/license-Unlicense-green) ![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-important)
3
+
4
+ # IMDB for the terminal
5
+
6
+ Finding the right moviie to watch or series to binge can be time consuming. My
7
+ girlfriend asked if I could create an application that would cut down the time
8
+ spent on searching in favor of time spent watching and cuddling. Meet
9
+ IMDB for the terminal.
10
+
11
+ Narrow down your preferences from a 1000 movies and almost 500 series. Select
12
+ a minimum IMDB rating, range of production years, genres you like and dislike
13
+ to get your preferred list. Get detailed information on movies and series and
14
+ where you can stream them. Even the movie poster in the terminal.
15
+
16
+ IMDB for the terminal uses three APIs - one to scrape IMDB for the top 1000
17
+ lists, one to give you details on the movies and series and one to show you
18
+ where you can stream it. When you first run the application, it will guide you
19
+ through the process of registering free accounts and get the three API keys
20
+ needed. I would be happy to keys in the application, but there is a limit to
21
+ the number of calls each day, so you need your own keys. The key to scrape the
22
+ IMDB top 1000 is via `page2api`. You will get a certain amounts of credits for
23
+ free when you sign up, and it will last you a very long time since the program
24
+ caches the movies and series lists. You could probably do well with refreshing
25
+ the lists once per month or so (using the key `I`).
26
+
27
+ As for the application itself, it's a terminal curses program that is best
28
+ installed with `gem install imdb-terminal` and run the program `imdb`in the
29
+ terminal. Or you can clone this repo and run the single file `imdb`. As usual,
30
+ I try to keep the requirements to a minimum and the program to a single file.
31
+
32
+ # Screenshot
33
+ The screenshot gives you an overview of the layout of the panes:
34
+ ![IMDB-terminal screenshot](img/screenshot.png)
35
+
36
+ # Keymap
37
+
38
+ Keys | Function
39
+ -----------|--------------------
40
+ TAB | Cycle the panes (active pane is gray)
41
+ Shift-TAB | Cycle backward
42
+ Arrow keys | UP, DOWN, PgUP, PgDOWN, HOME, END in lists
43
+ \+ or - | Depends on pane (intuitive)
44
+ I | Load fresh IMDB data (be patient)
45
+ m or s | Show MOVIES or SERIES
46
+ r | Select MINimum IMDB rating (in bottom line)
47
+ y or Y | Select MINimum or MAXimum production year
48
+ / or \\ | Enter or clear search for movie/series title
49
+ G | Set genres to match every movie/series
50
+ d | Get details on selected movie/series
51
+ D | Show where you can stream the movie/series
52
+ R | Refresh all panes
53
+ w or W | Write changes to config or save IMDB data
54
+ q or Q | Quit w/saving config or w/o saving config
55
+
56
+ # Suggestions and improvements
57
+ If you have any suggestions for improvement or cool features, create an issue
58
+ in this repository.
59
+
60
+ For more stuff, check out https://isene.com
data/bin/imdb ADDED
@@ -0,0 +1,787 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ # PROGRAM INFO
5
+ # Name: IMDB-term
6
+ # Language: Pure Ruby, best viewed in VIM
7
+ # Author: Geir Isene <g@isene.com>
8
+ # Web_site: http://isene.com/
9
+ # Github: https://github.com/isene/imdb-term
10
+ # License: I release all copyright claims. This code is in the public domain.
11
+ # Permission is granted to use, copy modify, distribute, and sell
12
+ # this software for any purpose. I make no guarantee about the
13
+ # suitability of this software for any purpose and I am not liable
14
+ # for any damages resulting from its use. Further, I am under no
15
+ # obligation to maintain or extend this software. It is provided
16
+ # on an 'as is' basis without any expressed or implied warranty.
17
+
18
+ # PRELIMINARIES
19
+ @help = <<HELPTEXT
20
+ IMDB-term (https://github.com/isene/IMDB) Help text:
21
+
22
+ Keys | Function
23
+ -----------+--------------------
24
+ TAB | Cycle the panes (active pane is gray)
25
+ Shift-TAB | Cycle backward
26
+ Arrow keys | UP, DOWN, PgUP, PgDOWN, HOME, END in lists
27
+ + or - | Depends on pane (intuitive)
28
+ I | Load fresh IMDB data (be patient)
29
+ m or s | Show MOVIES or SERIES
30
+ r | Select MINimum IMDB rating (in bottom line)
31
+ y or Y | Select MINimum or MAXimum production year
32
+ / or \\ | Enter or clear search for movie/series title
33
+ G | Set genres to match every movie/series
34
+ d | Get details on selected movie/series
35
+ D | Show where you can stream the movie/series
36
+ R | Refresh all panes
37
+ w or W | Write changes to config or save IMDB data
38
+ q or Q | Quit w/saving config or w/o saving config
39
+ HELPTEXT
40
+ begin # BASIC SETUP
41
+ require 'net/http'
42
+ require 'open-uri'
43
+ require 'rest_client'
44
+ require 'json'
45
+ require 'readline'
46
+ require 'io/console'
47
+ require 'curses'
48
+ include Curses
49
+
50
+ def cmd?(command)
51
+ system("which #{command} > /dev/null 2>&1")
52
+ end
53
+ if cmd?('/usr/lib/w3m/w3mimgdisplay')
54
+ @w3mimgdisplay = "/usr/lib/w3m/w3mimgdisplay"
55
+ @showimage = true
56
+ else
57
+ @showimage = false
58
+ end
59
+ @showimage = false unless (cmd?('xwininfo') and cmd?('xdotool'))
60
+
61
+ begin # Check if network is available
62
+ URI.open("https://www.google.com/", :open_timeout=>5)
63
+ rescue
64
+ puts "\nNo network. Running offline.\n\n"
65
+ end
66
+
67
+ # INITIALIZE BASIC VARIABLES
68
+ ## These can be set in .imdb.conf
69
+ @rating = 0
70
+ @yearMin = 0
71
+ @yearMax = 2100
72
+ @myMY = []
73
+ @myMN = []
74
+ @mySY = []
75
+ @mySN = []
76
+ ## These are IMDB specific
77
+ @urlmovies = "https://www.imdb.com/search/title/?groups=top_1000&start=[1, 1001, 50]"
78
+ @urlseries = "https://www.imdb.com/search/title/?title_type=tv_series&start=[1, 1001, 50]"
79
+ @country = "no"
80
+ @lang = "en"
81
+ @genres = ["Action", "Adult", "Adventure", "Animation", "Biography", "Comedy", "Crime", "Documentary", "Drama",
82
+ "Family", "Fantasy", "Film Noir", "Game Show", "History", "Horror", "Musical", "Music", "Mystery",
83
+ "News", "Reality-TV", "Romance", "Sci-Fi", "Short", "Sport", "Talk-Show", "Thriller", "War", "Western"]
84
+ @genY = @genres.map(&:clone)
85
+ @genN = []
86
+ ## These are internal variables
87
+ @imdbmovies = []
88
+ @imdbseries = []
89
+ @imdbsel = []
90
+ @search = ''
91
+ @new = false
92
+ @movies = true
93
+ @noimage = false
94
+
95
+ ## Color constants (e.g. Window x fg)
96
+ WtMfg = 232
97
+ WtSfg = 255
98
+ WtMbg = 214
99
+ WtMbgS = 202
100
+ WtSbg = 23
101
+ WtSbgS = 58
102
+ Wifg = 230
103
+ Wgfg = 195
104
+ WgYfg = 34
105
+ WgNfg = 241
106
+ Wmfg = 117
107
+ Wnfg = 204
108
+ Wdfg = 255
109
+ Wpfg = 242
110
+ Wbfg = 252
111
+ Wbbg = 238
112
+ Mark = 240
113
+ WAbg = 233
114
+ WIbg = 232
115
+ end
116
+
117
+ # CLASSES
118
+ class Curses::Window # CLASS EXTENSION
119
+ attr_accessor :fg, :bg, :attr, :text, :index, :list, :update
120
+ # General extensions (see https://github.com/isene/Ruby-Curses-Class-Extension)
121
+ def clr
122
+ self.setpos(0, 0)
123
+ self.maxy.times {self.deleteln()}
124
+ self.refresh
125
+ self.setpos(0, 0)
126
+ end
127
+ def fill # Fill window with color as set by :bg
128
+ self.setpos(0, 0)
129
+ self.bg = 0 if self.bg == nil
130
+ self.fg = 255 if self.fg == nil
131
+ xor = self.fg ^ self.bg
132
+ init_pair(xor, self.fg, self.bg)
133
+ blank = " " * self.maxx
134
+ self.maxy.times {self.attron(color_pair(xor)) {self << blank}}
135
+ self.refresh
136
+ self.setpos(0, 0)
137
+ end
138
+ def write # Write context of :text to window with attributes :attr
139
+ self.bg = 0 if self.bg == nil
140
+ self.fg = 255 if self.fg == nil
141
+ xor = self.fg ^ self.bg
142
+ init_pair(xor, self.fg, self.bg)
143
+ self.attr = 0 if self.attr == nil
144
+ self.attron(color_pair(xor) | self.attr) { self << self.text }
145
+ self.refresh
146
+ self.text = ""
147
+ end
148
+ def p(fg, bg, attr, text)
149
+ init_pair(fg ^ bg, fg, bg)
150
+ self.attron(color_pair(fg ^ bg) | attr) { self << text }
151
+ self.refresh
152
+ end
153
+ end
154
+
155
+ # GENERIC FUNCTIONS
156
+ def firstrun
157
+ puts "Welcome to IMDB-term, the IMDB application for the terminal."
158
+ puts "\nFind your next movie or series to binge. Narrow down your preferences from a 1000 movies and almost 500 series."
159
+ puts "Select a minimum IMDB rating, range of production years, genres you like and dislike to get your preferred list."
160
+ puts "Get detailed information on movies and series and where you can stream them. Even the movie poster in the terminal."
161
+ puts "\nLet's first look at the help text (accessible in the program via the key '?'):\n\n"
162
+ puts @help
163
+ print "\nPress any key... "; STDIN.getch
164
+ system("clear")
165
+ puts "We will now walk you through the steps you need to do to make use of this application:"
166
+ puts "\n 1. Go to the website https://www.page2api.com/"
167
+ print " Create a free account and paste your API_KEY here (then press ENTER): "
168
+ conf = "@imdbkey = '"
169
+ conf += gets.chomp + "'\n"
170
+ puts "\n 2. Go to the website https://www.omdbapi.com/apikey.aspx"
171
+ print " Create a free account and paste your API KEY here (then press ENTER): "
172
+ conf += "@omdbkey = '"
173
+ conf += gets.chomp + "'\n"
174
+ puts "\n 3. Go to the website https://rapidapi.com/movie-of-the-night-movie-of-the-night-default/api/streaming-availability"
175
+ print " Create a free account and paste your X-RapidAPI-Key here (then press ENTER): "
176
+ conf += "@streamkey = '"
177
+ conf += gets.chomp + "'\n"
178
+ File.write(Dir.home+'/.imdb.conf', conf)
179
+ puts "\n\nYour keys have now been written to the configuration file (.imdb.conf). You can edit this file manually if needed."
180
+ print "\nPress 'y' to start imdb-term "; y = STDIN.getch
181
+ exit if y != "y"
182
+ end
183
+ def getchr # PROCESS KEY PRESSES
184
+ c = STDIN.getch #(min: 0, time: 1)
185
+ case c
186
+ when "\e" # ANSI escape sequences
187
+ case $stdin.getc
188
+ when '[' # CSI
189
+ case $stdin.getc
190
+ when 'A' then chr = "UP"
191
+ when 'B' then chr = "DOWN"
192
+ when 'C' then chr = "RIGHT"
193
+ when 'D' then chr = "LEFT"
194
+ when 'Z' then chr = "S-TAB"
195
+ when '2' then chr = "INS" ; STDIN.getc
196
+ when '3' then chr = "DEL" ; STDIN.getc
197
+ when '5' then chr = "PgUP" ; STDIN.getc
198
+ when '6' then chr = "PgDOWN" ; STDIN.getc
199
+ when '7' then chr = "HOME" ; STDIN.getc
200
+ when '8' then chr = "END" ; STDIN.getc
201
+ end
202
+ end
203
+ when "", "" then chr = "BACK"
204
+ when "" then chr = "C-C"
205
+ when "" then chr = "C-G"
206
+ when "" then chr = "C-T"
207
+ when "" then chr = "LDEL"
208
+ when "" then chr = "WBACK"
209
+ when "\r" then chr = "ENTER"
210
+ when "\t" then chr = "TAB"
211
+ when /./ then chr = c
212
+ end
213
+ return chr
214
+ end
215
+ def getkey # GET KEY FROM USER
216
+ chr = getchr
217
+ case chr
218
+ when '?' # Show helptext in right window
219
+ @w_d.fill
220
+ @w_d.text = @help
221
+ @w_d.write
222
+ @w_d.update = false
223
+ when 'UP'
224
+ @active.index = @active.index <= 0 ? @active.list.size - 1 : @active.index - 1
225
+ when 'DOWN'
226
+ @active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
227
+ when 'PgUP'
228
+ @active.index -= @active.maxy - 2
229
+ @active.index = 0 if @active.index < 0
230
+ when 'PgDOWN'
231
+ @active.index += @active.maxy - 2
232
+ @active.index = @active.list.size - 1 if @active.index > @active.list.size
233
+ when 'HOME'
234
+ @active.index = 0
235
+ when 'END'
236
+ @active.index = @active.list.size - 1
237
+ when 'TAB'
238
+ case @active
239
+ when @w_i
240
+ @active = @w_g
241
+ when @w_g
242
+ @active = @w_m
243
+ when @w_m
244
+ @active = @w_n
245
+ when @w_n
246
+ @active = @w_i
247
+ end
248
+ when 'S-TAB'
249
+ case @active
250
+ when @w_i
251
+ @active = @w_n
252
+ when @w_n
253
+ @active = @w_m
254
+ when @w_m
255
+ @active = @w_g
256
+ when @w_g
257
+ @active = @w_i
258
+ end
259
+ when 'I'
260
+ w_b("Loading IMDB data...")
261
+ loadimdb
262
+ when 'm'
263
+ @movies = true
264
+ when 's'
265
+ @movies = false
266
+ when 'r'
267
+ r = w_b_getstr(" Set MINimum Rating: ", "")
268
+ @rating = r.to_f unless r == ""
269
+ when 'y'
270
+ y = w_b_getstr(" Set MINimum Year: ", "")
271
+ @yearMin = y.to_i unless y == ""
272
+ when 'Y'
273
+ y = w_b_getstr(" Set MAXimum Year: ", "")
274
+ @yearMax = y.to_i unless y == ""
275
+ when '/'
276
+ @search = w_b_getstr(" Search for title: ", "")
277
+ when '\\'
278
+ @search = ''
279
+ when 'G'
280
+ @genY = @genres.map(&:clone)
281
+ @genN = []
282
+ when '+'
283
+ case @active
284
+ when @w_i
285
+ @myY.push(@active.list[@active.index])
286
+ @active.index = 0 if @active.index > @active.list.size - 2
287
+ when @w_g
288
+ @genY.push(@active.list[@active.index]) unless @genN.include?(@active.list[@active.index])
289
+ @genN.delete(@active.list[@active.index])
290
+ @active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
291
+ when @w_m
292
+ @myN.delete(@active.list[@active.index])
293
+ when @w_n
294
+ @myY.push(@active.list[@active.index])
295
+ @myN.delete(@active.list[@active.index])
296
+ @active.index -= 1 if @active.index == @active.list.size
297
+ end
298
+ when '-'
299
+ case @active
300
+ when @w_i
301
+ @myN.push(@active.list[@active.index])
302
+ @active.index = 0 if @active.index > @active.list.size - 2
303
+ when @w_g
304
+ @genN.push(@active.list[@active.index]) unless @genY.include?(@active.list[@active.index])
305
+ @genY.delete(@active.list[@active.index])
306
+ @active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
307
+ when @w_m
308
+ @myY.delete(@active.list[@active.index])
309
+ @active.index -= 1 if @active.index == @active.list.size
310
+ when @w_n
311
+ @myN.delete(@active.list[@active.index])
312
+ @active.index -= 1 if @active.index == @active.list.size
313
+ end
314
+ when 'd'
315
+ @w_d.fill
316
+ w_d(1)
317
+ @w_d.update = false
318
+ when 'D'
319
+ @w_d.fill
320
+ w_d(2)
321
+ @w_d.update = false
322
+ when 'R' # Refresh all windows
323
+ @break = true
324
+ when '@' # Enter "Ruby debug"
325
+ cmd = w_b_getstr("◆ ", "")
326
+ begin
327
+ @w_b.text = eval(cmd)
328
+ @w_b.fill
329
+ @w_b.write
330
+ rescue StandardError => e
331
+ w_b("Error: #{e.inspect}")
332
+ end
333
+ @w_b.update = false
334
+ when 'w'
335
+ saveconf
336
+ when 'W'
337
+ saveimdb
338
+ when 'q' # Exit
339
+ saveconf
340
+ exit 0
341
+ when 'Q' # EXIT
342
+ exit 0
343
+ end
344
+ end
345
+ def getimdb(url)
346
+ api_url = "https://www.page2api.com/api/v1/scrape"
347
+ payload = {
348
+ api_key: @imdbkey,
349
+ batch: {
350
+ urls: url,
351
+ concurrency: 1,
352
+ merge_results: true
353
+ },
354
+ parse: {
355
+ movies: [
356
+ {
357
+ title: ".lister-item-header >> text",
358
+ url: ".lister-item-header a >> href",
359
+ id: ".userRatingValue >> data-tconst",
360
+ year: ".lister-item-year >> text",
361
+ genre: ".genre >> text",
362
+ votes: "[name=nv] >> text",
363
+ rating: ".ratings-imdb-rating >> data-value",
364
+ _parent: ".lister-item",
365
+ runtime: ".runtime >> text",
366
+ certificate: ".certificate >> text"
367
+ }
368
+ ]
369
+ },
370
+ datacenter_proxy: "us"
371
+ }
372
+ response = RestClient::Request.execute(
373
+ method: :post,
374
+ payload: payload.to_json,
375
+ url: api_url,
376
+ headers: { "Content-type" => "application/json" },
377
+ ).body
378
+ res = JSON.parse(response)
379
+ imdb = []
380
+ res["result"]["movies"].each {|m| imdb.push [m["title"].sub(/^\d+\. /, ''), m["rating"].to_f, m["year"].delete("^0-9").to_i, m["genre"], m["id"]]}
381
+ imdb
382
+ end
383
+ def loadimdb
384
+ @imdbmovies = getimdb(@urlmovies)
385
+ @imdbseries = getimdb(@urlseries)
386
+ @new = true
387
+ end
388
+ def imdbmovies
389
+ if @search == ''
390
+ @imdbsel = @imdbmovies.map{|m| m if (m[1] >= @rating) and ((@yearMin..@yearMax) === m[2])}
391
+ else
392
+ @imdbsel = @imdbmovies.map{|m| m if (m[0] =~ /#{@search}/)}
393
+ end
394
+ @imdbsel.select! do |i|
395
+ ig = i[3].split(", ") unless i == nil
396
+ (ig & @genY).any? unless ig == nil
397
+ end
398
+ @imdbsel.select! {|i| (i[3].split(", ") & @genN).empty?}
399
+ @imdbsel = @imdbsel - @myMN - @myMY
400
+ @myMYsel = @myMY - @myMN
401
+ [@imdbsel, @myMYsel, @myMY, @myMN, @genY, @genN].each do |arr|
402
+ arr.uniq!
403
+ arr.compact!
404
+ arr.sort_by! {|m| m[0]}
405
+ end
406
+ @w_i.list = @imdbsel
407
+ @w_i.index = 0 if @w_i.index > @w_i.list.size - 2
408
+ @w_m.list = @myMYsel
409
+ @w_n.list = @myMN
410
+ @myN = @myMN
411
+ @myY = @myMY
412
+ end
413
+ def imdbseries
414
+ if @search == ''
415
+ @imdbsel = @imdbseries.map{|m| m if (m[1] > @rating) and ((@yearMin..@yearMax) === m[2])}
416
+ else
417
+ @imdbsel = @imdbseries.map{|m| m if (m[0] =~ /#{@search}/)}
418
+ end
419
+ @imdbsel.select! do |i|
420
+ ig = i[3].split(", ") unless i == nil
421
+ (ig & @genY).any? unless ig == nil
422
+ end
423
+ @imdbsel.select! {|i| (i[3].split(", ") & @genN).empty?}
424
+ @imdbsel = @imdbsel - @mySN - @mySY
425
+ @mySYsel = @mySY - @mySN
426
+ [@imdbsel, @mySYsel, @mySY, @mySN, @genY, @genN].each do |arr|
427
+ arr.uniq!
428
+ arr.compact!
429
+ arr.sort_by! {|m| m[0]}
430
+ end
431
+ @w_i.list = @imdbsel
432
+ @w_i.index = 0 if @w_i.index > @w_i.list.size - 2
433
+ @w_m.list = @mySYsel
434
+ @w_n.list = @mySN
435
+ @myN = @mySN
436
+ @myY = @mySY
437
+ end
438
+ def getomdb(id)
439
+ @urldetails = "http://www.omdbapi.com/?apikey=#{@omdbkey}&i=#{id}"
440
+ details = Net::HTTP.get(URI(@urldetails))
441
+ det = JSON.parse(details)
442
+ return det
443
+ end
444
+ def getstreaming(id) # Returns array of outlets
445
+ url = URI("https://streaming-availability.p.rapidapi.com/v2/get/basic?country=#{@country}&imdb_id=#{id}&output_language=#{@lang}")
446
+ http = Net::HTTP.new(url.host, url.port)
447
+ http.use_ssl = true
448
+ request = Net::HTTP::Get.new(url)
449
+ request["X-RapidAPI-Key"] = @streamkey
450
+ request["X-RapidAPI-Host"] = 'streaming-availability.p.rapidapi.com'
451
+ response = http.request(request)
452
+ res = JSON.parse(response.read_body)
453
+ outlets = []
454
+ begin
455
+ res["result"]["streamingInfo"][@country].each{|k,v| outlets.push(k)}
456
+ return outlets
457
+ rescue
458
+ return ["No outlet info"]
459
+ end
460
+ end
461
+ def saveconf
462
+ if File.exist?(Dir.home+'/.imdb.conf')
463
+ conf = File.read(Dir.home+'/.imdb.conf')
464
+ else
465
+ conf = ""
466
+ end
467
+ conf.gsub!(/^@rating.*\n/, "")
468
+ conf += "@rating = #{@rating}\n"
469
+ conf.gsub!(/^@yearMin.*\n/, "")
470
+ conf += "@yearMin = #{@yearMin}\n"
471
+ conf.gsub!(/^@yearMax.*\n/, "")
472
+ conf += "@yearMax = #{@yearMax}\n"
473
+ conf.gsub!(/^@genY.*\n/, "")
474
+ conf += "@genY = #{@genY}\n"
475
+ conf.gsub!(/^@genN.*\n/, "")
476
+ conf += "@genN = #{@genN}\n"
477
+ conf.gsub!(/^@myMY.*\n/, "")
478
+ conf += "@myMY = #{@myMY}\n"
479
+ conf.gsub!(/^@myMN.*\n/, "")
480
+ conf += "@myMN = #{@myMN}\n"
481
+ conf.gsub!(/^@mySY.*\n/, "")
482
+ conf += "@mySY = #{@mySY}\n"
483
+ conf.gsub!(/^@mySN.*\n/, "")
484
+ conf += "@mySN = #{@mySN}\n"
485
+ w_b("Configuration written to .imdb.conf")
486
+ File.write(Dir.home+'/.imdb.conf', conf)
487
+ end
488
+ def saveimdb
489
+ if File.exist?(Dir.home+'/.imdb')
490
+ data = File.read(Dir.home+'/.imdb')
491
+ else
492
+ data = ""
493
+ end
494
+ data.gsub!(/^@imdbmovies.*\n/, "")
495
+ data += "@imdbmovies = #{@imdbmovies}\n"
496
+ data.gsub!(/^@imdbseries.*\n/, "")
497
+ data += "@imdbseries = #{@imdbseries}\n"
498
+ w_b("IMDB data written to .imdb")
499
+ File.write(Dir.home+'/.imdb', data)
500
+ end
501
+
502
+ # BASIC WINDOW FUNCTIONS
503
+ def w_t # SHOW INFO IN @w_t
504
+ @movies ? @w_t.text = " MOVIES :: " : @w_t.text = " SERIES :: "
505
+ @w_t.text += "Rating MIN: #{@rating} - Year MIN: #{@yearMin} - Year MAX: #{@yearMax} :: Selection = #{@imdbsel.size}"
506
+ @w_t.write
507
+ end
508
+ def w_list(win) # LIST IN WINDOW
509
+ win == @active ? win.bg = WAbg : win.bg = WIbg
510
+ win.fill
511
+ ix = 0
512
+ ix = win.index - win.maxy/2 if win.index > win.maxy/2 and win.list.size > win.maxy - 1
513
+ while ix < win.list.size and ix < win.maxy do
514
+ str = win.list[ix][0]
515
+ str = win.list[ix] if win == @w_g
516
+ if ix == win.index and win == @active
517
+ win.text = "→ "
518
+ win.write
519
+ else
520
+ win.attr = 0
521
+ win.text = " "
522
+ win.write
523
+ end
524
+ if win == @w_g
525
+ if @genY.include?(str)
526
+ win.text = "+"
527
+ win.fg = WgYfg
528
+ win.attr = Curses::A_BOLD
529
+ win.write
530
+ elsif @genN.include?(str)
531
+ win.text = "-"
532
+ win.fg = WgNfg
533
+ win.write
534
+ else
535
+ win.text = " "
536
+ win.write
537
+ win.fg = Wgfg
538
+ end
539
+ end
540
+ win.attr = win.attr | Curses::A_UNDERLINE if ix == win.index
541
+ str = str[0..(win.maxx - 6)] + "…" if str.length > win.maxx - 4
542
+ win.text = str
543
+ win.write
544
+ win.attr = 0
545
+ win.text = " " * (win.maxx - str.length - 2)
546
+ win.text = win.text[0..-2] if win == @w_g
547
+ win.write
548
+ ix += 1
549
+ end
550
+ if win.index > win.maxy/2
551
+ win.setpos(0, win.maxx - 1)
552
+ win.text = "∆"
553
+ fg0 = win.fg
554
+ win.fg = Mark
555
+ win.write
556
+ win.fg = fg0
557
+ end
558
+ if win.list.length > win.maxy - 1 and win.list.length > win.index + win.maxy/2 - 1
559
+ win.setpos(win.maxy - 1, win.maxx - 1)
560
+ win.text = "∇"
561
+ fg0 = win.fg
562
+ win.fg = Mark
563
+ win.write
564
+ win.fg = fg0
565
+ end
566
+ end
567
+ def w_d(ext = 0) # SHOW INFO IN @w_d and @w_p
568
+ return if @active == @w_g
569
+ list = @active.list
570
+ return if list.empty? # Skip if list is empty
571
+ id = list[@active.index][4]
572
+ @w_d.text = "#{list[@active.index][0]}\n\n"
573
+ @w_d.attr = Curses::A_BOLD
574
+ @w_d.write
575
+ @w_d.attr = 0
576
+ @w_d.text += "Rating: " + list[@active.index][1].to_s.ljust(14) + "Genres: #{list[@active.index][3]}\n"
577
+ @w_d.write
578
+ return unless ext > 0 # Skip if no details are to be displayed
579
+ det = getomdb(id)
580
+ otl = getstreaming(id)
581
+ @w_d.text = "Rated: " + det["Rated"].ljust(14) + "Runtime: #{det["Runtime"]}"
582
+ @w_d.text += " (#{det["totalSeasons"]})" unless det["totalSeasons"] == nil
583
+ @w_d.text += "\n"
584
+ @w_d.text += "Released: " + det["Released"].ljust(14) + "(#{id})\n\n"
585
+ width = Curses.cols - 104
586
+ @w_d.text += det["Plot"].gsub!(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
587
+ @w_d.text += "\n"
588
+ @w_d.text += "Awards: " + det["Awards"] + "\n"
589
+ @w_d.text += "Director: " + det["Director"] + "\n"
590
+ @w_d.text += "Actors: " + det["Actors"] + "\n\n"
591
+ @w_d.text += "Metascore: " + det["Metascore"] + "\n\n"
592
+ outlets = ""
593
+ @w_d.write
594
+ # Display the poster
595
+ poster = det["Poster"]
596
+ `curl -s "#{poster}" > /tmp/imdb.jpg`
597
+ image = "/tmp/imdb.jpg"
598
+ begin
599
+ terminfo = `xwininfo -id $(xdotool getactivewindow)`
600
+ term_w = terminfo.match(/Width: (\d+)/)[1].to_i
601
+ term_h = terminfo.match(/Height: (\d+)/)[1].to_i
602
+ char_w = term_w / Curses.cols
603
+ char_h = term_h / Curses.lines
604
+ img_x = char_w * 105
605
+ img_y = char_h * (Curses.lines / 2 + 2)
606
+ img_max_w = char_w * (Curses.cols - width - 2)
607
+ img_max_h = char_h * (@w_d.maxy - 2)
608
+ # Clear previous images
609
+ `echo "6;#{img_x};#{img_y};#{img_max_w+2};#{img_max_h+2};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
610
+ img_w,img_h = `identify -format "%[fx:w]x%[fx:h]" #{image} 2>/dev/null`.split('x')
611
+ img_w = img_w.to_i
612
+ img_h = img_h.to_i
613
+ if img_w > img_max_w
614
+ img_h = img_h * img_max_w / img_w
615
+ img_w = img_max_w
616
+ end
617
+ if img_h > img_max_h
618
+ img_w = img_w * img_max_h / img_h
619
+ img_h = img_max_h
620
+ end
621
+ `echo "0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;\"#{image}\"\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
622
+ rescue
623
+ w_b("Error showing image")
624
+ end
625
+ return unless ext > 1 # Skip if no outlets are to be displayed
626
+ otl.each{|o| outlets += "#{o} "}
627
+ @w_d.text += "Outlets: " + outlets
628
+ @w_d.write
629
+ end
630
+ def imageclear
631
+ begin
632
+ terminfo = `xwininfo -id $(xdotool getactivewindow)`
633
+ term_w = terminfo.match(/Width: (\d+)/)[1].to_i
634
+ term_h = terminfo.match(/Height: (\d+)/)[1].to_i
635
+ char_w = term_w / Curses.cols
636
+ char_h = term_h / Curses.lines
637
+ img_x = char_w * 105
638
+ img_y = char_h * (Curses.lines / 2 + 2)
639
+ img_max_w = char_w * (Curses.cols - (Curses.cols - 104) - 2)
640
+ img_max_h = char_h * (@w_d.maxy - 2)
641
+ `echo "6;#{img_x};#{img_y};#{img_max_w+2};#{img_max_h+2};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
642
+ rescue
643
+ w_b("Error clearing image")
644
+ end
645
+ end
646
+
647
+ # BOTTOM WINDOW FUNCTIONS
648
+ def w_b(info) # SHOW INFO IN @W_B
649
+ @w_b.clr
650
+ info = "Choose window: i=IMDB list (+/- to add/remove from My list), g=Genres (+/- to add/remove), m=My list. " if info == nil
651
+ info = info[1..(@w_b.maxx - 3)] + "…" if info.length + 3 > @w_b.maxx
652
+ info += " " * (@w_b.maxx - info.length) if info.length < @w_b.maxx
653
+ @w_b.text = info
654
+ @w_b.write
655
+ @w_b.update = false
656
+ end
657
+ def w_b_getstr(pretext, text) # A SIMPLE READLINE-LIKE ROUTINE
658
+ Curses.curs_set(1)
659
+ Curses.echo
660
+ stk = 0
661
+ pos = text.length
662
+ chr = ""
663
+ while chr != "ENTER"
664
+ @w_b.setpos(0,0)
665
+ @w_b.text = pretext + text
666
+ @w_b.text += " " * (@w_b.maxx - text.length) if text.length < @w_b.maxx
667
+ @w_b.write
668
+ @w_b.setpos(0,pretext.length + pos)
669
+ @w_b.refresh
670
+ chr = getchr
671
+ case chr
672
+ when 'C-C', 'C-G'
673
+ return ""
674
+ when 'RIGHT'
675
+ pos += 1 unless pos > text.length
676
+ when 'LEFT'
677
+ pos -= 1 unless pos == 0
678
+ when 'HOME'
679
+ pos = 0
680
+ when 'END'
681
+ pos = text.length
682
+ when 'DEL'
683
+ text[pos] = ""
684
+ when 'BACK'
685
+ unless pos == 0
686
+ pos -= 1
687
+ text[pos] = ""
688
+ end
689
+ when 'LDEL'
690
+ text = ""
691
+ pos = 0
692
+ when /^.$/
693
+ text.insert(pos,chr)
694
+ pos += 1
695
+ end
696
+ end
697
+ Curses.curs_set(0)
698
+ Curses.noecho
699
+ return text
700
+ end
701
+
702
+ # BEFORE WE START
703
+ begin
704
+ if File.exist?(Dir.home+'/.imdb.conf')
705
+ load(Dir.home+'/.imdb.conf')
706
+ else
707
+ firstrun
708
+ end
709
+ if File.exist?(Dir.home+'/.imdb')
710
+ load(Dir.home+'/.imdb')
711
+ else
712
+ puts "Loading IMDB data... (this may take som time, go get some coffee)"
713
+ loadimdb
714
+ saveimdb
715
+ end
716
+ ## Curses setup
717
+ Curses.init_screen
718
+ Curses.start_color
719
+ Curses.curs_set(0)
720
+ Curses.noecho
721
+ Curses.cbreak
722
+ Curses.stdscr.keypad = true
723
+ end
724
+
725
+ # MAIN PROGRAM
726
+ loop do # OUTER LOOP - (catching refreshes via 'r')
727
+ @break = false # Initialize @break variable (set if user hits 'r')
728
+ begin # Create the windows/panels
729
+ Curses.stdscr.bg = 234 # Use for borders
730
+ Curses.stdscr.fill
731
+ maxx = Curses.cols
732
+ maxy = Curses.lines
733
+ # Curses::Window.new( h, w, y, x )
734
+ @w_t = Curses::Window.new( 1, maxx, 0, 0 )
735
+ @w_i = Curses::Window.new( maxy-2, 40, 1, 0 )
736
+ @w_g = Curses::Window.new( maxy-2, 20, 1, 41 )
737
+ @w_m = Curses::Window.new( maxy/2-1, 40, 1, 62 )
738
+ @w_n = Curses::Window.new( maxy/2-1, 40, maxy/2+1, 62 )
739
+ @w_d = Curses::Window.new( maxy/2-1, maxx-103, 1, 103 )
740
+ @w_p = Curses::Window.new( maxy/2-1, maxx-103, maxy/2+1, 103 )
741
+ @w_b = Curses::Window.new( 1, maxx, maxy-1, 0 )
742
+ @w_i.fg, @w_i.bg = Wifg, WIbg
743
+ @w_g.fg, @w_g.bg = Wgfg, WIbg
744
+ @w_m.fg, @w_m.bg = Wmfg, WIbg
745
+ @w_n.fg, @w_n.bg = Wnfg, WIbg
746
+ @w_d.fg, @w_d.bg = Wdfg, WIbg
747
+ @w_p.fg, @w_p.bg = Wpfg, WIbg
748
+ @w_b.fg, @w_b.bg = Wbfg, Wbbg
749
+ [@w_i, @w_g, @w_m, @w_n].each{|w| w.index = 0}
750
+ @w_b.fill
751
+ @w_g.list = @genres
752
+ @w_b.update = true
753
+ @w_d.update = true
754
+ @active = @w_i
755
+ loop do # INNER, CORE LOOP
756
+ if @movies
757
+ imdbmovies
758
+ @w_t.fg = WtMfg
759
+ @search == '' ? @w_t.bg = WtMbg : @w_t.bg = WtMbgS
760
+ @w_t.attr = Curses::A_BOLD
761
+ else
762
+ imdbseries
763
+ @w_t.fg = WtSfg
764
+ @search == '' ? @w_t.bg = WtSbg : @w_t.bg = WtSbgS
765
+ @w_t.attr = Curses::A_BOLD
766
+ end
767
+ @w_t.fill; @w_i.fill; @w_g.fill; @w_m.fill; @w_n.fill; @w_p.fill
768
+ w_t; w_list(@w_i); w_list(@w_g); w_list(@w_m); w_list(@w_n)
769
+ if @w_d.update
770
+ @w_d.fill
771
+ w_d
772
+ end
773
+ imageclear if @w_d.update
774
+ @w_d.update = true
775
+ w_b(nil) if @w_b.update
776
+ @w_b.update = true
777
+ getkey # Get key from user
778
+ break if @break # Break to outer loop, redrawing windows, if user hit 'r'
779
+ break if Curses.cols != maxx or Curses.lines != maxy # break on terminal resize
780
+ end
781
+ ensure # On exit: clear image, close curses
782
+ image_show("clear")
783
+ close_screen
784
+ end
785
+ end
786
+
787
+ # vim: set sw=2 sts=2 et fdm=syntax fdn=2 fcs=fold\:\ :
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imdb-terminal
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Geir Isene
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.3.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.2
33
+ description: Narrow down your preferences from a 1000 movies and almost 500 series.
34
+ Select a minimum IMDB rating, range of production years, genres you like and dislike
35
+ to get your preferred list. Get detailed information on movies and series and where
36
+ you can stream them. Even the movie poster in the terminal.
37
+ email: g@isene.com
38
+ executables:
39
+ - imdb
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - README.md
44
+ - bin/imdb
45
+ homepage: https://isene.com/
46
+ licenses:
47
+ - Unlicense
48
+ metadata:
49
+ source_code_uri: https://github.com/isene/imdb
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.3.5
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: IMDB for the terminal
69
+ test_files: []