imdb-terminal 0.1

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.
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: []