fetch-player-dependencies.cr 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. require "http"
  2. require "yaml"
  3. require "digest/sha1"
  4. require "option_parser"
  5. require "colorize"
  6. # Taken from https://crystal-lang.org/api/1.1.1/OptionParser.html
  7. minified = false
  8. OptionParser.parse do |parser|
  9. parser.banner = "Usage: Fetch VideoJS dependencies [arguments]"
  10. parser.on("-m", "--minified", "Use minified versions of VideoJS dependencies (performance and bandwidth benefit)") { minified = true }
  11. parser.on("-h", "--help", "Show this help") do
  12. puts parser
  13. exit
  14. end
  15. parser.invalid_option do |flag|
  16. STDERR.puts "ERROR: #{flag} is not a valid option."
  17. STDERR.puts parser
  18. exit(1)
  19. end
  20. end
  21. required_dependencies = File.open("videojs-dependencies.yml") do |file|
  22. YAML.parse(file).as_h
  23. end
  24. def update_versions_yaml(required_dependencies, minified, dep_name)
  25. File.open("assets/videojs/#{dep_name}/versions.yml", "w") do |io|
  26. YAML.build(io) do |builder|
  27. builder.mapping do
  28. # Versions
  29. builder.scalar "version"
  30. builder.scalar "#{required_dependencies[dep_name]["version"]}"
  31. builder.scalar "minified"
  32. builder.scalar minified
  33. end
  34. end
  35. end
  36. end
  37. # The first step is to check which dependencies we'll need to install.
  38. # If the version we have requested in `videojs-dependencies.yml` is the
  39. # same as what we've installed, we shouldn't do anything. Likewise, if it's
  40. # different or the requested dependency just isn't present, then it needs to be
  41. # installed.
  42. # Since we can't know when videojs-youtube-annotations is updated, we'll just always fetch
  43. # a new copy each time.
  44. dependencies_to_install = [] of String
  45. required_dependencies.keys.each do |dep|
  46. dep = dep.to_s
  47. path = "assets/videojs/#{dep}"
  48. # Check for missing dependencies
  49. if !Dir.exists?(path)
  50. Dir.mkdir(path)
  51. dependencies_to_install << dep
  52. else
  53. config = File.open("#{path}/versions.yml") do |file|
  54. YAML.parse(file).as_h
  55. end
  56. if config["version"].as_s != required_dependencies[dep]["version"].as_s || config["minified"].as_bool != minified
  57. `rm -rf #{path}/*.js #{path}/*.css`
  58. dependencies_to_install << dep
  59. end
  60. end
  61. end
  62. # Now we begin the fun part of installing the dependencies.
  63. # But first we'll setup a temp directory to store the plugins
  64. tmp_dir_path = "#{Dir.tempdir}/invidious-videojs-dep-install"
  65. Dir.mkdir(tmp_dir_path) if !Dir.exists? tmp_dir_path
  66. channel = Channel(String | Exception).new
  67. dependencies_to_install.each do |dep|
  68. spawn do
  69. dep_name = dep
  70. download_path = "#{tmp_dir_path}/#{dep}"
  71. dest_path = "assets/videojs/#{dep}"
  72. HTTP::Client.get("https://registry.npmjs.org/#{dep}/-/#{dep}-#{required_dependencies[dep]["version"]}.tgz") do |response|
  73. Dir.mkdir(download_path)
  74. data = response.body_io.gets_to_end
  75. File.write("#{download_path}/package.tgz", data)
  76. # https://github.com/iv-org/invidious/pull/2397#issuecomment-922375908
  77. if `sha1sum #{download_path}/package.tgz`.split(" ")[0] != required_dependencies[dep]["shasum"]
  78. raise Exception.new("Checksum for '#{dep}' failed")
  79. end
  80. end
  81. # Unless we install an external dependency, crystal provides no way of extracting a tarball.
  82. # Thus we'll go ahead and call a system command.
  83. `tar -vzxf '#{download_path}/package.tgz' -C '#{download_path}'`
  84. raise "Extraction for #{dep} failed" if !$?.success?
  85. # Would use File.rename in the following steps but for some reason it just doesn't work here.
  86. # Video.js itself is structured slightly differently
  87. dep = "video" if dep == "video.js"
  88. # This dep nests everything under an additional JS or CSS folder
  89. if dep == "silvermine-videojs-quality-selector"
  90. js_path = "js/"
  91. # It also stores their quality selector as `quality-selector.css`
  92. `mv #{download_path}/package/dist/css/quality-selector.css #{dest_path}/quality-selector.css`
  93. else
  94. js_path = ""
  95. end
  96. # Would use File.rename but for some reason it just doesn't work here.
  97. if minified && File.exists?("#{download_path}/package/dist/#{js_path}#{dep}.min.js")
  98. `mv #{download_path}/package/dist/#{js_path}#{dep}.min.js #{dest_path}/#{dep}.js`
  99. else
  100. `mv #{download_path}/package/dist/#{js_path}#{dep}.js #{dest_path}/#{dep}.js`
  101. end
  102. # Fetch CSS which isn't guaranteed to exist
  103. #
  104. # Also, video JS changes structure here once again...
  105. dep = "video-js" if dep == "video"
  106. # VideoJS marker uses a dot on the CSS files.
  107. dep = "videojs.markers" if dep == "videojs-markers"
  108. if File.exists?("#{download_path}/package/dist/#{dep}.css")
  109. if minified && File.exists?("#{download_path}/package/dist/#{dep}.min.css")
  110. `mv #{download_path}/package/dist/#{dep}.min.css #{dest_path}/#{dep}.css`
  111. else
  112. `mv #{download_path}/package/dist/#{dep}.css #{dest_path}/#{dep}.css`
  113. end
  114. end
  115. # Update/create versions file for the dependency
  116. update_versions_yaml(required_dependencies, minified, dep_name)
  117. channel.send(dep_name)
  118. rescue ex
  119. channel.send(ex)
  120. end
  121. end
  122. if dependencies_to_install.empty?
  123. puts "#{"Player".colorize(:blue)} #{"dependencies".colorize(:green)} are satisfied"
  124. else
  125. puts "#{"Resolving".colorize(:green)} #{"player".colorize(:blue)} dependencies"
  126. dependencies_to_install.size.times do
  127. result = channel.receive
  128. if result.is_a? Exception
  129. raise result
  130. end
  131. puts "#{"Fetched".colorize(:green)} #{result.colorize(:blue)}"
  132. end
  133. end
  134. # Cleanup
  135. `rm -rf #{tmp_dir_path}`