From 56b63f8d09dce82c10bb00eef9dbe813089ad177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Zurcher?= Date: Thu, 25 Jul 2024 18:18:05 +0200 Subject: update tmpdb --- tmdb.rb | 498 ++++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 297 insertions(+), 201 deletions(-) diff --git a/tmdb.rb b/tmdb.rb index 0f8c309..33d501b 100755 --- a/tmdb.rb +++ b/tmdb.rb @@ -3,231 +3,327 @@ require 'json' require 'open-uri' +# tmdb [movie_path] [db_path] n = ARGV.size -mpath=File.dirname(__FILE__) -mpath = ARGV.shift if n > 0 -dbpath=File.join(mpath, 'db') -dbpath = ARGV.shift if n > 1 - -# TODO indicate if srt file is here +mpath = n.positive? ? ARGV.shift : File.dirname(__FILE__) +dbpath = n > 1 ? ARGV.shift : File.join(mpath, 'db') puts " movies : #{mpath}\n db : #{dbpath}" -API_KEY = 'c4202eaa738af60ae7a784c349a0cc63' -SEARCH_M="https://api.themoviedb.org/3/search/movie?api_key=#{API_KEY}&language=en-US&page=1&include_adult=true&" -FETCH_M="https://api.themoviedb.org/3/movie/ID?api_key=#{API_KEY}" -FETCH_C="https://api.themoviedb.org/3/movie/ID/credits?api_key=#{API_KEY}" -URL_M='https://www.themoviedb.org/movie/' -URL_A='https://www.themoviedb.org/person/' -URL_I='https://www.themoviedb.org/t/p/original/' -DB=File.join(dbpath, 'db.json') -FIX=File.join(dbpath, 'fix.json') -FAIL=File.join(dbpath, 'failed.json') -DBA=File.join(dbpath, 'a') -DBM=File.join(dbpath, 'm') -HTML_I=File.join(dbpath, 'index.html') -HTML_M=File.join(dbpath, 'movies.html') -HTML_A=File.join(dbpath, 'actors.html') -BLANK='blank.png' - -Dir.mkdir(dbpath) if not Dir.exist?(dbpath) -Dir.mkdir(DBA) if not Dir.exist?(DBA) -Dir.mkdir(DBM) if not Dir.exist?(DBM) - -@failed = [] -@fix = File.exist?(FIX) ? JSON.load(File.read(FIX)) : {} -@prev_db = File.exist?(DB) ? JSON.load(File.read(DB)) : [] -@idx = @prev_db.collect { |m| m['filename'] } -@current_db = [] - -def search fn - b, e = fn.split '.' - name, *more = b.split '-' - year = more[-1] - sequel = more[0] if more.size == 2 - puts "search : #{name} - #{year} - #{sequel}" - begin - res = JSON.load URI.open(SEARCH_M + URI.encode_www_form('query' => name)).read - rescue - return nil +API_KEY = JSON.parse(File.read(File.expand_path('~/.tmdb.json')))['apikey'] +API_URL = 'https://api.themoviedb.org/3'.freeze +SEARCH_M = "#{API_URL}/search/movie?api_key=#{API_KEY}&language=en-US&page=1&include_adult=true&".freeze +SEARCH_T = SEARCH_M.sub(%r{/movie}, '/tv').freeze +FETCH_M = "#{API_URL}/movie/ID?api_key=#{API_KEY}".freeze +FETCH_T = FETCH_M.sub(%r{/movie}, '/tv').freeze +FETCH_CM = "#{API_URL}/movie/ID/credits?api_key=#{API_KEY}".freeze +FETCH_CT = FETCH_CM.sub(%r{/movie}, '/tv').freeze +URL_M = 'https://www.themoviedb.org/movie/'.freeze +URL_T = URL_M.sub(%r{/movie}, '/tv').freeze +URL_A = 'https://www.themoviedb.org/person/'.freeze +URL_I = 'https://www.themoviedb.org/t/p/original/'.freeze +ACTORS_N = 6 +ASZ = 150 +MSZ = 350 +BLANK = 'blank.png'.freeze +DB = File.join(dbpath, 'db.json') +FIX = File.join(dbpath, 'fix.json') +FAIL = File.join(dbpath, 'failed.json') +DBA = File.join(dbpath, 'a') +DBM = File.join(dbpath, 'm') +HTML_I = File.join(dbpath, 'index.html') +HTML_M = File.join(dbpath, 'movies.html') +HTML_A = File.join(dbpath, 'actors.html') +HTML_F = File.join(dbpath, 'failed.html') +HTML_N = File.join(dbpath, 'new.html') + +Dir.mkdir(dbpath) unless Dir.exist?(dbpath) +Dir.mkdir(DBA) unless Dir.exist?(DBA) +Dir.mkdir(DBM) unless Dir.exist?(DBM) + +def write_db(next_db) + puts "write #{DB}" + File.open(DB, 'w') { |f| f << next_db.to_json } +end + +def num + ('1'..'9').inject('') { |r, i| "#{r}" } +end + +def alpha + ('A'..'Z').inject('') { |r, i| "#{r}" } +end + +def menu(*links) + t = '' +end + +def prelude(title) + f = "#{title}\n" + f << '' + f << '' + f << "\n" +end + +def write_index(next_db) + puts "write #{HTML_I}" + File.open(HTML_I, 'w') do |f| + f << prelude('Movies Index') + f << menu('Movies', 'Actors', 'New', 'Failed') + f << '
' + f << "
#{alpha}
\n" + f << "
#{num}
\n" + letter = nil + next_db.each do |m| + l = m['title'][0].upcase + if l != letter + f << '
' unless letter.nil? + letter = l + f << "
\n
#{letter}
\n" + end + f << "\n" + f << " \n" + end + f << '
#{m['title']}#{' - TV' if m['is_tv']}#{m['release_date'][0..3]}
' end - return nil if res['total_results'] == 0 - sel = res['results'].select { |r| (r['release_date']||'nope')[0..3] == year } - return nil if sel.size == 0 - if sel.size == 1 - puts " found '#{sel[0]['title']}'" - else - puts " found #{sel.map {|s| s['title'] + ' ' + s['release_date']||'?' }}" - s = sel.select { |r| r['title'].downcase == name } - sel = s if s.size > 0 - s = sel.select { |r| r['title'].downcase.gsub(/[^ a-z]/, '') =~ /#{name.gsub(/[^ a-z]/, '')}/ } if sel.size != 1 - sel = s if s.size > 0 - sel = sel.select { |r| r['title'].downcase =~ /#{sequel}/ } if sel.size != 1 - return nil if sel.size != 1 - puts " choose '#{sel[0]['title']}'" +end + +def write_actors(actors) + puts "write #{HTML_A}" + File.open(HTML_A, 'w') do |f| + f << prelude('Actors') + f << menu('Index', 'Movies', 'New', 'Failed') + f << '
' + f << "
#{alpha}
\n" + f << "
#{num}
\n" + letter = nil + actors.keys.sort! { |a, b| a.downcase <=> b.downcase }.each do |aname| + l = aname[0].upcase + if l != letter + f << '
' unless letter.nil? + letter = l + f << "
\n
#{letter}
\n" + end + d = actors[aname] + d['movies'].sort! { |a, b| b[2] <=> a[2] } + m = d['movies'].shift + f << "\n" + f << "" + f << "" + d['movies'].each do |mov| + f << "" + f << "" + end + end + f << '
#{aname}#{m[1]}#{m[2][0..3]}
 #{mov[2][0..3]}
' end - sel[0] end -def download id, p, base - return nil if p.nil? - fn = id.to_s + File.extname(p) - dst = File.join(base, fn) - if not File.exist? dst - puts " download #{URL_I + p} -> #{dst}" - File.open(dst, 'wb') { |f| f.write URI.open(URL_I + p).read } +def __write_movies(fout, movies, actors = nil) + movies.each do |m| + url = m['is_tv'] ? URL_T : URL_M + img = (m['img'].nil? ? BLANK : "m/#{m['img']}") + fout << "
" + fout << "
" + fout << '
' + fout << "
#{m['title']}
" + fout << "
(#{m['original_title']})
" if m['title'] != m['original_title'] + if m['is_tv'] + fout << "
#{m['release_date'][0..3]}-#{m['last_air_date'][0..3]}
" + fout << "
#{m['eps']}/#{m['number_of_episodes']} episodes -#{m['number_of_seasons']} seasons
" + else + fout << "
#{m['release_date'][0..3]}
" + end + # fout << "
[#{m['fname']}]
" + fout << "
\n" + m['cast'].each do |a| + img = a['img'] + img = (img.nil? ? BLANK : "a/#{img}") + fout << "

#{a['name']}

" + fout << "
\n" + unless actors.nil? + actors[a['name']] ||= { 'id' => a['id'], 'movies' => [] } + actors[a['name']]['movies'] << [m['id'], m['title'], m['release_date']] + end + end + fout << "
#{m['overview']}
\n" + fout << "
\n" end - fn end -def fetch id, fn, m - m['filename'] = fn - m['img'] = download(id, m['poster_path'], DBM) - m['cast'] = [] - JSON.load(URI.open(FETCH_C.sub(/ID/, id.to_s)).read)['cast'].each_with_index do |a, i| - break if i > 6 - a['img'] = download(a['id'], a['profile_path'], DBA) - m['cast'] << a +def write_movies(movies) + puts "write #{HTML_M}" + actors = {} + File.open(HTML_M, 'w') do |f| + f << prelude('Movies') + f << menu('Index', 'Actors', 'New', 'Failed') << "\n" + __write_movies(f, movies, actors) + f << '' end - m + actors end -def known? fn - id = @fix[fn] - if not id.nil? - m = @prev_db.find{ |i| i['id'] == id and i['filename'] == fn } - m = JSON.load URI.open(FETCH_M.sub(/ID/, id.to_s)).read if m.nil? - @current_db << fetch(id, fn, m) - return nil +def write_failed(failed) + puts "write #{HTML_F}" + File.open(HTML_F, 'w') do |f| + f << prelude('Failed') + f << menu('Index', 'Actors', 'New', 'Failed') << "\n" + failed.each do |fn| + f << "
#{fn}
\n" + end + f << '' end - if @idx.include? fn - @current_db << @prev_db.find{ |i| i['filename'] == fn } - return nil +end + +def write_new(movies) + puts "write #{HTML_N}" + File.open(HTML_N, 'w') do |f| + f << prelude('New') + f << menu('Index', 'Movies', 'Actors', 'Failed') << "\n" + __write_movies(f, movies) + f << '' end +end + +def download(id, path, base) + return nil if path.nil? + + fn = id.to_s + File.extname(path) + dst = File.join(base, fn) + unless File.exist? dst + puts " get : #{dst}" + File.open(dst, 'wb') { |f| f.write URI(URL_I + path).open.read } + end + system("magick #{dst} -resize x#{MSZ} #{dst}") if base == DBM + system("magick #{dst} -resize #{ASZ}x #{dst}") if base == DBA fn end -Dir.glob(File.join(mpath, '*')) do |path| - next if File.directory? path - next if path =~ /\.srt$/ or path =~ /\.rb$/ or path =~ /\.sub$/ or path =~ /\.jpg$/ - fn = path.split('/')[-1] - next if known?(fn).nil? - fs = fn.gsub('_', ' ').downcase.tr('àáâäçèéêëìíîïòóôöùúûü','aaaaceeeeiiiioooouuuu') - m = search fs - if m.nil? - puts ' failed' - @failed << fn - else - @current_db << fetch(m['id'], fn, m) +def get_all(data) + id = data['id'] + data['img'] = download(id, data['poster_path'], DBM) + data['cast'] = [] + url = data['is_tv'] ? FETCH_CT : FETCH_CM + JSON.parse(URI(url.sub(/ID/, id.to_s)).open.read)['cast'] + .sort { |a, b| a['order'] <=> b['order'] }.each_with_index do |a, i| + break if i == ACTORS_N + + a['img'] = download(a['id'], a['profile_path'], DBA) + data['cast'] << a end + data end -CSS=-< b['title'].downcase } -File.open(DB, 'w') { |f| f << @current_db.to_json } -File.open(HTML_I, 'w') do |f| - f << 'Movies Index\n\n
" - f << "
    " + ('A'..'Z').inject('') {|r,i| r+ "
  • #{i}
  • "} + '
' - letter=nil - @current_db.each do |m| - l = m['title'][0].upcase - if l != letter - letter = l - f << '' if not letter.nil? - f << "
#{letter}
" - end - f << "" +def filter_results(res, data) + sel = res.select { |r| (r['release_date'] || 'nope')[0..3] == data[:year] } + return nil if sel.empty? + + if sel.size > 1 + puts " #{sel.map { |s| "#{s['title']} #{s['release_date'] || '?'}" }.join("\n ")}" + s = sel.select { |r| r['stitle'] =~ /#{data[:sequel]}/ } unless data[:sequel].nil? + sel = s unless s.nil? || s&.empty? + s = sel.select { |r| r['stitle'] == data[:name] } + sel = s unless s.empty? + return nil if sel.size != 1 + end - f << '
#{m['release_date'][0..3]}
' -end -actors = {} -File.open(HTML_M, 'w') do |f| - f << 'My Movies\n" - @current_db.each do |m| - img = m['img'] - img = (img.nil? ? BLANK : ('m/' + img)) - f << "
" - f << "
" - f << "
#{m['title']}
" - f << "
(#{m['original_title']})
" if m['title'] != m['original_title'] - f << "
- #{m['release_date'][0..3]}
" - f << "
[#{m['filename']}]
\n" - m['cast'].each do |a| - img = a['img'] - img = (img.nil? ? BLANK : ('a/' + img)) - f << "

#{a['original_name']}

\n" - actors[a['name']] ||= {'id'=>a['id'], 'movies'=>[]} - actors[a['name']]['movies'] << [m['id'], m['title'], m['release_date']] - end - f << "
#{m['overview']}
\n" - f << "
\n" + + puts " => : '#{sel[0]['title']}' #{sel[0]['id']}" + sel[0]['id'] +end + +def normalise_results(res) + res.each do |r| + r['release_date'] = r['first_air_date'] unless r.key? 'release_date' + r['title'] = r['name'] unless r.key? 'title' + r['original_title'] = r['original_name'] unless r.key? 'original_title' + r['stitle'] = r['title'].downcase.gsub(/[^ a-z0-9]/, '') end - f << '' -end - -File.open(HTML_A, 'w') do |f| - f << 'Actors Index\n\n
" - f << "
    " + ('A'..'Z').inject('') {|r,i| r+ "
  • #{i}
  • "} + '
' - letter=nil - actors.keys.sort! {|a,b| a.downcase <=> b.downcase }.each do |aname| - l = aname[0].upcase - if l != letter - letter = l - f << '' if not letter.nil? - f << "
#{letter}
" - end - d = actors[aname] - d['movies'].sort! { |a,b| b[2] <=> a[2] } - m = d['movies'].shift - f << "" - f << "" - d['movies'].each do |m| - f << "" - end + res +end + +def fetch(data) + url = data['is_tv'] ? FETCH_T : FETCH_M + [] << JSON.parse(URI(url.sub(/ID/, data['id'].to_s)).open.read) +end + +def search(data) + url = data['is_tv'] ? SEARCH_T : SEARCH_M + res = JSON.parse(URI.parse(url + URI.encode_www_form('query' => data[:name])).read)['results'] + normalise_results(res) + filter_results(res, data) +end + +def process_fname(data) + puts "#{data['fname']} :" + fname = data['fname'].downcase.tr('àáâäçèéêëìíîïòóôöùúûü', 'aaaaceeeeiiiioooouuuu').gsub('_', ' ') + fname = fname[..-5] unless data['is_tv'] + name, *more = fname.split '-' + year = more[-1] + sequel = more[0] if more.size == 2 + data.merge(name: name, year: year, sequel: sequel) +end + +def process(data) + data = process_fname(data) + data['id'] = search(data) if data['id'].nil? + return nil if data['id'].nil? + + data.merge!(normalise_results(fetch(data))[0]) + get_all(data) +end + +def in_prev_db?(fname, prev_db) + prev_db.find { |i| i['fname'] == fname } if @db_idx.include?(fname) +end + +failed = [] +fix_db = File.exist?(FIX) ? JSON.parse(File.read(FIX)) : {} +prev_db = File.exist?(DB) ? JSON.parse(File.read(DB)) : [] +@db_idx = prev_db.collect { |m| m['fname'] } +next_db = [] + +def skip?(fname) + fname =~ /\.srt$/ || fname =~ /\.sub$/ || fname =~ /\.jpg$/ || fname =~ /^db/ +end + +def eps(path) + n = 0 + Dir.glob(File.join(path, '*')) do |fn| + n += 1 unless skip?(fn) end - f << '
#{m[2][0..3]}
 #{m[2][0..3]}
' + n end -puts "FAILED :" -File.open(FAIL, 'w') { |f| f << @failed.to_json } -@failed.each { |fn| puts " -> #{fn}" } +# FIXME: list seasons -> x/N +Dir.glob(File.join(mpath, '*')) do |path| + fname = path.split('/')[-1] + next if skip?(fname) + + data = in_prev_db?(fname, prev_db) + if data.nil? + is_tv = File.directory?(path) + mtime = File.mtime(path).to_i.to_s + eps = is_tv ? eps(path) : 0 + data = process('id' => fix_db[fname], 'fname' => fname, 'is_tv' => is_tv, 'eps' => eps, 'mtime' => mtime) + end + failed << fname if data.nil? + next_db << data unless data.nil? +end + +next_db.sort! { |a, b| a['title'].downcase <=> b['title'].downcase } +write_db(next_db) +write_index(next_db) +write_actors(write_movies(next_db)) +write_failed(failed) +write_new(next_db.sort { |a, b| b['mtime'] <=> a['mtime'] }[..20]) + +puts 'FAILED :' +File.open(FAIL, 'w') { |f| f << failed.to_json } +failed.each { |fn| puts " -> #{fn}" } + +# jq '.[] | select(.fname | test("Fargo"))' db.json +# curl --request GET --url 'https://api.themoviedb.org/3/tv/60622/season/2/credits?api_key=c4202eaa738af60ae7a784c349a0cc63' | jq '.cast' +# 'https://api.themoviedb.org/3/tv/series_id/season/season_number/credits?languag -- cgit v1.1-2-g2b99