imdb-terminal 2.0.1 → 2.1.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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/imdb +77 -56
  3. metadata +26 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a835730544b1acf9055d609a37a5727aecb9754878be3ccf1b873502b72fd771
4
- data.tar.gz: 6a9bc9712e20f70d2eceb8169dc32d81263a0a65cf2418815a3774a05399aeda
3
+ metadata.gz: 8b26870720d0e55ae88bc797de5e902b24bca1deac0514c3c29058b90df31d08
4
+ data.tar.gz: 39c9433b18a602fa2f7b16493ae94841896da14493b24256852c1bd70253a403
5
5
  SHA512:
6
- metadata.gz: cf5bc22eb1a504498cb0a0843066b32c6fd0aaae995894f01616a27cebc3daac6c1c2bc996f28b7178c53a68dc9993f1dd1b4850118ea84fb42ebc2678b670f8
7
- data.tar.gz: 51ae69f5e824dd50157bb2f770d8536ffb9121957166fb61710d281d09c6041f794b5b41a520b2d6c7aa56359592974ff7e098c5315cd4e8510411086a0bd2a5
6
+ metadata.gz: 92396d67a67433e37f25fa135577f012f1259c88e7cc859f868eec153705a35cd138fb57aeb26132927d82b502d3ebf40dab9ae0ee6999a6fb0e951b71a2ed0c
7
+ data.tar.gz: 556c17abd3c6e6b2b5aaebb6a5b1e179cb16374f82945ce2fa484185bebfb9eaf0a2329d892a9ed0cd3ee12987a22536e6dba767034a241feee07eda0d9af66f
data/bin/imdb CHANGED
@@ -14,7 +14,7 @@
14
14
  # for any damages resulting from its use. Further, I am under no
15
15
  # obligation to maintain or extend this software. It is provided
16
16
  # on an 'as is' basis without any expressed or implied warranty.
17
- # Version: 2.0.0: Breaking change - requires rcurses 6.0.0+ with explicit init
17
+ # Version: 2.1.1: Add HTTP timeouts and external command error handling
18
18
 
19
19
  # REQUIRES AND CONSTANTS {{{1
20
20
  require 'io/console'
@@ -32,6 +32,7 @@ require 'nokogiri'
32
32
  require 'fileutils'
33
33
  require 'concurrent'
34
34
  require 'rcurses'
35
+ require 'termpix'
35
36
 
36
37
  include Rcurses
37
38
  include Rcurses::Cursor
@@ -84,8 +85,9 @@ class IMDBApp
84
85
  @list.border = true
85
86
  @last_poster_id = nil
86
87
  @current_image = nil
88
+ @termpix = Termpix::Display.new unless WINDOWS
87
89
  @last_active_window = nil
88
-
90
+
89
91
  run_loop
90
92
  ensure
91
93
  save_config
@@ -409,7 +411,8 @@ class IMDBApp
409
411
  uri = "https://www.imdb.com/#{path}/"
410
412
 
411
413
  begin
412
- raw = `curl -sfL -H "User-Agent: #{ua}" "#{uri}"`
414
+ raw = `curl -sfL --max-time 15 --connect-timeout 10 -H "User-Agent: #{ua}" "#{uri}"`
415
+ return [] unless $?.success?
413
416
  html = raw.force_encoding('UTF-8').scrub('')
414
417
  doc = Nokogiri::HTML(html)
415
418
 
@@ -452,10 +455,19 @@ class IMDBApp
452
455
 
453
456
  def scrape_json_ld(path, ua) #{{{2
454
457
  uri = "https://www.imdb.com/#{path}/"
455
- raw = `curl -sfL -H "User-Agent: #{ua}" "#{uri}"`
458
+ begin
459
+ raw = `curl -sfL --max-time 15 --connect-timeout 10 -H "User-Agent: #{ua}" "#{uri}"`
460
+ unless $?.success?
461
+ @progress.say("Warning: Failed to fetch #{path}") if defined?(@progress) && @progress
462
+ return []
463
+ end
464
+ rescue => e
465
+ @progress.say("Warning: curl error for #{path}: #{e.message}") if defined?(@progress) && @progress
466
+ return []
467
+ end
456
468
  html = raw.force_encoding('UTF-8').scrub('')
457
469
  doc = Nokogiri::HTML(html)
458
-
470
+
459
471
  if (ld = doc.at_css('script[type="application/ld+json"]'))
460
472
  begin
461
473
  data = JSON.parse(ld.text)
@@ -473,7 +485,7 @@ class IMDBApp
473
485
  # fall back to HTML scrape
474
486
  end
475
487
  end
476
-
488
+
477
489
  doc.css('.lister-list tr').map do |tr|
478
490
  rating = tr.at_css('td.ratingColumn strong')&.text.to_f || 0.0
479
491
  a = tr.at_css('td.titleColumn a') or next
@@ -767,16 +779,27 @@ class IMDBApp
767
779
  def download_poster(tconst, cache) #{{{2
768
780
  url = fetch_imdb_poster_url(tconst)
769
781
  return unless url
770
-
782
+
771
783
  dest = File.join(cache, "#{tconst}.jpg")
772
784
  return if File.exist?(dest) && File.size(dest) > 1000
773
-
785
+
774
786
  cmd = [
775
- 'curl', '-sfL', '--max-time', '10', '--retry', '2', '--retry-delay', '1',
787
+ 'curl', '-sfL', '--max-time', '10', '--connect-timeout', '10',
788
+ '--retry', '2', '--retry-delay', '1',
776
789
  '-H', 'User-Agent: Mozilla/5.0', '-o', dest, url
777
790
  ].map { |arg| Shellwords.escape(arg) }.join(' ')
778
-
779
- system(cmd, err: File::NULL)
791
+
792
+ begin
793
+ system(cmd, err: File::NULL)
794
+ # Remove incomplete downloads
795
+ if File.exist?(dest) && File.size(dest) < 100
796
+ File.delete(dest) rescue nil
797
+ end
798
+ rescue => e
799
+ File.open("/tmp/imdb_fetch_errors.log", "a") do |f|
800
+ f.puts "[#{Time.now.iso8601}] Poster download failed for #{tconst}: #{e.message}"
801
+ end
802
+ end
780
803
  end
781
804
 
782
805
  def fetch_imdb_poster_url(tconst) #{{{2
@@ -784,9 +807,13 @@ class IMDBApp
784
807
  html = URI.open(
785
808
  "https://www.imdb.com/title/#{tconst}/",
786
809
  "User-Agent" => "Mozilla/5.0",
787
- "Accept-Encoding" => "identity"
810
+ "Accept-Encoding" => "identity",
811
+ open_timeout: 10,
812
+ read_timeout: 10
788
813
  ).read
789
- rescue SocketError, Errno::ECONNREFUSED, OpenURI::HTTPError, Net::HTTPBadResponse
814
+ rescue SocketError, Errno::ECONNREFUSED, OpenURI::HTTPError,
815
+ Net::HTTPBadResponse, Net::OpenTimeout, Net::ReadTimeout,
816
+ Timeout::Error, Errno::ETIMEDOUT
790
817
  return nil
791
818
  end
792
819
  html[/<meta property="og:image" content="([^"]+)"/,1]
@@ -1188,12 +1215,14 @@ class IMDBApp
1188
1215
  uri.query = URI.encode_www_form(q: query, s: 'tt')
1189
1216
  html = nil
1190
1217
  begin
1191
- Timeout.timeout(5) do
1218
+ Timeout.timeout(15) do
1192
1219
  html = URI.open(uri.to_s,
1193
1220
  'User-Agent' => 'Mozilla/5.0',
1194
1221
  'Accept' => 'text/html,application/xhtml+xml',
1195
1222
  'Accept-Language' => 'en-US,en;q=0.9',
1196
- 'Accept-Encoding' => 'identity'
1223
+ 'Accept-Encoding' => 'identity',
1224
+ open_timeout: 10,
1225
+ read_timeout: 10
1197
1226
  ).read
1198
1227
  # Fix encoding issues
1199
1228
  html = html.force_encoding('UTF-8').scrub('?')
@@ -1324,7 +1353,7 @@ class IMDBApp
1324
1353
  help_text << " • Dump list removes items from main view permanently\n".fg(230)
1325
1354
  help_text << " • TMDb provides streaming provider information\n".fg(230)
1326
1355
  unless WINDOWS
1327
- help_text << " • Posters are displayed in the terminal using w3mimgdisplay\n".fg(230)
1356
+ help_text << " • Posters are displayed using termpix (Sixel or w3m protocols)\n".fg(230)
1328
1357
  else
1329
1358
  help_text << " • Poster display not available on Windows\n".fg(230)
1330
1359
  end
@@ -1811,43 +1840,33 @@ class IMDBApp
1811
1840
 
1812
1841
  def show_poster(tconst) #{{{2
1813
1842
  return if WINDOWS # Skip image display on Windows for now
1814
-
1815
- w3m = "/usr/lib/w3m/w3mimgdisplay"
1816
- return unless File.executable?(w3m)
1843
+ return unless @termpix&.supported?
1844
+
1817
1845
  cache = cache_dir
1818
1846
  file = File.join(cache, "#{tconst}.jpg")
1819
1847
 
1820
1848
  begin
1821
1849
  Timeout.timeout(2) do
1822
- info = `xwininfo -id $(xdotool getactivewindow) 2>/dev/null`
1823
- return unless info =~ /Width:\s*(\d+).*Height:\s*(\d+)/m
1824
- term_w, term_h = $1.to_i, $2.to_i
1825
1850
  rows, cols = IO.console.winsize
1826
- cw = term_w.to_f / cols
1827
- ch = term_h.to_f / rows
1828
- px = ((@detail.x - 1) * cw).to_i
1829
- py = (25 * ch).to_i
1830
- max_w = (40 * cw).to_i
1831
- max_h = ((rows - 28) * ch).to_i
1832
1851
 
1833
- `echo "6;#{px};#{py};#{max_w+4};#{max_h+4};\n4;\n3;" | #{w3m} 2>/dev/null`
1852
+ # Always clear previous poster first to prevent overlapping
1853
+ @termpix.clear(
1854
+ x: @detail.x - 1,
1855
+ y: 25,
1856
+ width: 40,
1857
+ height: rows - 27,
1858
+ term_width: cols,
1859
+ term_height: rows)
1834
1860
 
1835
1861
  if File.exist?(file) && File.size?(file) > 0
1836
- iw, ih = `identify -format "%wx%h" #{file}`.split('x').map(&:to_i)
1837
-
1838
- if iw > max_w
1839
- ih = ih * max_w / iw; iw = max_w
1840
- end
1841
- if ih > max_h
1842
- iw = iw * max_h / ih; ih = max_h
1843
- end
1844
-
1845
- `echo "0;1;#{px};#{py};#{iw};#{ih};;;;;\"#{file}\"\n4;\n3;" | #{w3m} 2>/dev/null`
1846
-
1847
- # Track the current image
1862
+ # Display new poster using termpix
1863
+ @termpix.show(file,
1864
+ x: @detail.x - 1,
1865
+ y: 25,
1866
+ max_width: 40,
1867
+ max_height: rows - 28)
1848
1868
  @current_image = file
1849
1869
  else
1850
- # Clear current image if no file exists
1851
1870
  @current_image = nil
1852
1871
  end
1853
1872
  end
@@ -1858,24 +1877,26 @@ class IMDBApp
1858
1877
 
1859
1878
  def check_image_redraw #{{{2
1860
1879
  return if WINDOWS # Skip image redraw on Windows
1880
+ return unless @termpix&.supported?
1861
1881
  # Only check if we have an image currently displayed
1862
1882
  return unless @current_image && File.exist?(@current_image)
1863
-
1883
+
1864
1884
  begin
1865
- # Check if we're in the active window - if not, image overlay might be cleared
1866
- active_window = `xdotool getactivewindow 2>/dev/null`.chomp
1867
- return if active_window.empty?
1868
-
1869
- # Check if this terminal window is the active one
1870
- current_window = `xdotool getwindowfocus 2>/dev/null`.chomp
1871
-
1872
- # Only redraw if we detect a focus change (simple heuristic)
1873
- if @last_active_window && @last_active_window != active_window && current_window == active_window
1874
- # Window focus changed and we're now active - redraw image using last poster id
1875
- show_poster(@last_poster_id) if @last_poster_id
1885
+ # For w3m protocol, check if image overlay was cleared
1886
+ if @termpix.protocol == :w3m
1887
+ active_window = `xdotool getactivewindow 2>/dev/null`.chomp
1888
+ return if active_window.empty?
1889
+
1890
+ current_window = `xdotool getwindowfocus 2>/dev/null`.chomp
1891
+
1892
+ # Only redraw if we detect a focus change
1893
+ if @last_active_window && @last_active_window != active_window && current_window == active_window
1894
+ show_poster(@last_poster_id) if @last_poster_id
1895
+ end
1896
+
1897
+ @last_active_window = active_window
1876
1898
  end
1877
-
1878
- @last_active_window = active_window
1899
+ # Sixel images persist, no redraw needed
1879
1900
  rescue
1880
1901
  # Silently fail - we don't want focus checking to break anything
1881
1902
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imdb-terminal
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-19 00:00:00.000000000 Z
11
+ date: 2026-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: termpix
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: nokogiri
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,12 +80,12 @@ dependencies:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '13.0'
69
- description: 'Discover and manage movies and TV series from IMDb''s Top 250 lists.
70
- Features smart search with preview mode, advanced filtering by rating/year/genre,
83
+ description: 'IMDB v2.1.1: Modern poster display using termpix gem with Sixel and
84
+ w3m protocol support. Discover and manage movies and TV series from IMDb''s Top
85
+ 250 lists. Features smart search with preview mode, advanced filtering by rating/year/genre,
71
86
  streaming information via TMDb, wish lists, and terminal poster display. Enhanced
72
- with jump-to-existing items, duplicate management, and robust data handling. Version
73
- 2.0.1: Added Windows support - all core functionality works on Windows with graceful
74
- handling of platform-specific features.'
87
+ with jump-to-existing items, duplicate management, robust data handling, and improved
88
+ HTTP timeout/error handling.'
75
89
  email: g@isene.com
76
90
  executables:
77
91
  - imdb
@@ -90,11 +104,11 @@ metadata:
90
104
  post_install_message: "✓ IMDb Terminal Browser installed successfully!\n\nTo get started:\n1.
91
105
  Run: imdb\n2. On first run, the app will scrape IMDb Top 250 lists\n3. Optional:
92
106
  Get a free TMDb API key for streaming info\n4. Press '?' for help once running\n\nExternal
93
- dependencies for full functionality (Linux/macOS only):\n- w3m-img (for poster display):
94
- sudo apt install w3m-img\n- imagemagick (for poster processing): sudo apt install
95
- imagemagick\n- xdotool (for image redraw on workspace switch): sudo apt install
96
- xdotool\n\nNote: Poster display is not available on Windows, but all other \nfunctionality
97
- works normally.\n\nEnjoy discovering your next favorite movie! :-)\n"
107
+ dependencies for full functionality (Linux/macOS only):\n- imagemagick (for image
108
+ processing): sudo apt install imagemagick\n- For w3m protocol: w3m-img, xdotool
109
+ (sudo apt install w3m-img xdotool)\n- For Sixel protocol (mlterm, xterm): imagemagick
110
+ convert command\n\nNote: Poster display is not available on Windows, but all other
111
+ \nfunctionality works normally.\n\nEnjoy discovering your next favorite movie! :-)\n"
98
112
  rdoc_options: []
99
113
  require_paths:
100
114
  - "."