rill-node 0.0.0-stage → 2026.11.14

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 (82) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +64 -2
  4. package/binding.gyp +31 -0
  5. package/index.d.ts +45 -0
  6. package/index.js +112 -0
  7. package/native/darwin-arm64/librill.dylib +0 -0
  8. package/native/darwin-arm64/rill-media +0 -0
  9. package/native/darwin-arm64/rill_node.node +0 -0
  10. package/native/darwin-x64/librill.dylib +0 -0
  11. package/native/darwin-x64/rill-media +0 -0
  12. package/native/darwin-x64/rill_node.node +0 -0
  13. package/native/linux-arm64-gnu/librill.so +0 -0
  14. package/native/linux-arm64-gnu/rill-media +0 -0
  15. package/native/linux-arm64-gnu/rill_node.node +0 -0
  16. package/native/linux-x64-gnu/librill.so +0 -0
  17. package/native/linux-x64-gnu/rill-media +0 -0
  18. package/native/linux-x64-gnu/rill_node.node +0 -0
  19. package/native-src/include/rill.h +90 -0
  20. package/native-src/include/rill_media.h +22 -0
  21. package/native-src/shard.yml +9 -0
  22. package/native-src/src/c_api.cr +194 -0
  23. package/native-src/src/cli.cr +637 -0
  24. package/native-src/src/rill/c_api/job.cr +336 -0
  25. package/native-src/src/rill/c_api/memory.cr +32 -0
  26. package/native-src/src/rill/c_api/status.cr +25 -0
  27. package/native-src/src/rill/c_api/sync.cr +125 -0
  28. package/native-src/src/rill/c_api/worker_pool.cr +403 -0
  29. package/native-src/src/rill/cancellation.cr +29 -0
  30. package/native-src/src/rill/client.cr +52 -0
  31. package/native-src/src/rill/cookie_jar.cr +130 -0
  32. package/native-src/src/rill/dash/manifest.cr +279 -0
  33. package/native-src/src/rill/dash/parser.cr +227 -0
  34. package/native-src/src/rill/download_options.cr +96 -0
  35. package/native-src/src/rill/download_plan.cr +27 -0
  36. package/native-src/src/rill/download_progress.cr +16 -0
  37. package/native-src/src/rill/downloader.cr +662 -0
  38. package/native-src/src/rill/errors.cr +155 -0
  39. package/native-src/src/rill/extractor.cr +18 -0
  40. package/native-src/src/rill/extractor_registry.cr +98 -0
  41. package/native-src/src/rill/extractors/bandcamp.cr +265 -0
  42. package/native-src/src/rill/extractors/dash.cr +77 -0
  43. package/native-src/src/rill/extractors/direct_media.cr +73 -0
  44. package/native-src/src/rill/extractors/generic.cr +441 -0
  45. package/native-src/src/rill/extractors/hls.cr +202 -0
  46. package/native-src/src/rill/extractors/internet_archive.cr +180 -0
  47. package/native-src/src/rill/extractors/m3u.cr +81 -0
  48. package/native-src/src/rill/extractors/media_ccc_de.cr +219 -0
  49. package/native-src/src/rill/extractors/mixcloud.cr +495 -0
  50. package/native-src/src/rill/extractors/peer_tube.cr +226 -0
  51. package/native-src/src/rill/extractors/rumble.cr +325 -0
  52. package/native-src/src/rill/extractors/soundcloud.cr +553 -0
  53. package/native-src/src/rill/extractors/spotify.cr +184 -0
  54. package/native-src/src/rill/extractors/twitch_clip.cr +273 -0
  55. package/native-src/src/rill/extractors/youtube.cr +520 -0
  56. package/native-src/src/rill/format.cr +82 -0
  57. package/native-src/src/rill/format_selector.cr +143 -0
  58. package/native-src/src/rill/hls/parser.cr +257 -0
  59. package/native-src/src/rill/hls/playlist.cr +100 -0
  60. package/native-src/src/rill/http_client.cr +503 -0
  61. package/native-src/src/rill/http_url.cr +28 -0
  62. package/native-src/src/rill/json_value.cr +66 -0
  63. package/native-src/src/rill/media.cr +151 -0
  64. package/native-src/src/rill/media_muxer.cr +333 -0
  65. package/native-src/src/rill/mime_type.cr +58 -0
  66. package/native-src/src/rill/output_file_name.cr +91 -0
  67. package/native-src/src/rill/playlist.cr +109 -0
  68. package/native-src/src/rill/request_options.cr +179 -0
  69. package/native-src/src/rill/segment_assembly.cr +70 -0
  70. package/native-src/src/rill/timestamp.cr +34 -0
  71. package/native-src/src/rill/version.cr +3 -0
  72. package/native-src/src/rill.cr +47 -0
  73. package/package.json +48 -4
  74. package/scripts/build-native.js +46 -0
  75. package/scripts/install-gyp.js +10 -0
  76. package/scripts/install.js +19 -0
  77. package/scripts/lib.js +1 -0
  78. package/scripts/libdir.js +23 -0
  79. package/scripts/package-native.js +35 -0
  80. package/scripts/platform.js +41 -0
  81. package/scripts/prepack.js +47 -0
  82. package/src/addon.c +498 -0
@@ -0,0 +1,109 @@
1
+ require "json"
2
+ require "uri"
3
+
4
+ require "./errors"
5
+ require "./media"
6
+
7
+ module Rill
8
+ class PlaylistEntry
9
+ getter url : URI
10
+ getter title : String?
11
+ getter id : String?
12
+ getter media : Media?
13
+ getter error : String?
14
+
15
+ def initialize(
16
+ @url : URI,
17
+ @title : String? = nil,
18
+ @id : String? = nil,
19
+ @media : Media? = nil,
20
+ @error : String? = nil,
21
+ )
22
+ end
23
+
24
+ def to_json(json : JSON::Builder) : Nil
25
+ json.object do
26
+ json.field "url", url.to_s
27
+ json.field "title", title
28
+ json.field "id", id
29
+ json.field "error", error
30
+ json.field "media" do
31
+ if item = media
32
+ item.to_json(json)
33
+ else
34
+ json.null
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ class Playlist
42
+ getter id : String?
43
+ getter title : String
44
+ getter description : String?
45
+ getter webpage_url : URI
46
+ getter extractor : String
47
+ getter entries : Array(PlaylistEntry)
48
+
49
+ def initialize(
50
+ @title : String,
51
+ @webpage_url : URI,
52
+ @extractor : String,
53
+ @entries : Array(PlaylistEntry),
54
+ @id : String? = nil,
55
+ @description : String? = nil,
56
+ )
57
+ end
58
+
59
+ def validate! : Nil
60
+ if title.empty?
61
+ raise InvalidExtractionError.new(webpage_url.to_s, "playlist title is empty")
62
+ end
63
+
64
+ if entries.empty?
65
+ raise InvalidExtractionError.new(webpage_url.to_s, "playlist has no entries")
66
+ end
67
+
68
+ entries.each do |entry|
69
+ validate_entry!(entry)
70
+ end
71
+ end
72
+
73
+ def to_json(json : JSON::Builder) : Nil
74
+ json.object do
75
+ json.field "id", id
76
+ json.field "title", title
77
+ json.field "description", description
78
+ json.field "webpage_url", webpage_url.to_s
79
+ json.field "extractor", extractor
80
+ json.field "entries" do
81
+ json.array do
82
+ entries.each(&.to_json(json))
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def to_pretty_json : String
89
+ JSON.build(indent: 2) do |json|
90
+ to_json(json)
91
+ end
92
+ end
93
+
94
+ private def validate_entry!(entry : PlaylistEntry) : Nil
95
+ scheme = entry.url.scheme
96
+ if scheme != "http" && scheme != "https"
97
+ raise InvalidExtractionError.new(webpage_url.to_s, "entry has a non-HTTP URL")
98
+ end
99
+
100
+ if entry.url.host.nil?
101
+ raise InvalidExtractionError.new(webpage_url.to_s, "entry has no host")
102
+ end
103
+
104
+ if entry.media && entry.error
105
+ raise InvalidExtractionError.new(webpage_url.to_s, "entry has both media and an error")
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,179 @@
1
+ require "http/headers"
2
+ require "json"
3
+
4
+ require "./errors"
5
+ require "./version"
6
+
7
+ module Rill
8
+ class RequestOptions
9
+ DEFAULT_USER_AGENT = "rill/#{VERSION}"
10
+ DEFAULT_CONNECT_TIMEOUT = 15.seconds
11
+ DEFAULT_READ_TIMEOUT = 30.seconds
12
+ DEFAULT_MAX_REDIRECTS = 10
13
+ DEFAULT_RETRIES = 2
14
+ DEFAULT_RETRY_DELAY = 1.second
15
+ DEFAULT_MAX_RESPONSE_BYTES = 16_i64 * 1024 * 1024
16
+
17
+ property user_agent : String = DEFAULT_USER_AGENT
18
+ property headers : HTTP::Headers = HTTP::Headers.new
19
+ property cookies : Hash(String, String) = {} of String => String
20
+ property connect_timeout : Time::Span = DEFAULT_CONNECT_TIMEOUT
21
+ property read_timeout : Time::Span = DEFAULT_READ_TIMEOUT
22
+ property max_redirects : Int32 = DEFAULT_MAX_REDIRECTS
23
+ property retries : Int32 = DEFAULT_RETRIES
24
+ property retry_delay : Time::Span = DEFAULT_RETRY_DELAY
25
+ property max_response_bytes : Int64 = DEFAULT_MAX_RESPONSE_BYTES
26
+
27
+ def retry_wait(attempt : Int32) : Time::Span
28
+ exponent = Math.min(attempt - 1, 5)
29
+ @retry_delay * (1 << exponent)
30
+ end
31
+
32
+ def self.from_json(source : String) : RequestOptions
33
+ object = begin
34
+ JSON.parse(source)
35
+ rescue JSON::ParseException
36
+ raise InvalidOptionsError.new("options are not valid JSON")
37
+ end.as_h?
38
+
39
+ unless object
40
+ raise InvalidOptionsError.new("options must be a JSON object")
41
+ end
42
+
43
+ options = new
44
+
45
+ object.each do |key, value|
46
+ case key
47
+ when "user_agent"
48
+ options.user_agent = string_option(key, value)
49
+ when "headers"
50
+ apply_headers(options, value)
51
+ when "cookies"
52
+ apply_cookies(options, value)
53
+ when "connect_timeout_seconds"
54
+ options.connect_timeout = seconds_option(key, value)
55
+ when "read_timeout_seconds"
56
+ options.read_timeout = seconds_option(key, value)
57
+ when "retry_delay_seconds"
58
+ options.retry_delay = seconds_option(key, value, allow_zero: true)
59
+ when "max_redirects"
60
+ options.max_redirects = int32_option(key, value)
61
+ when "retries"
62
+ options.retries = int32_option(key, value)
63
+ when "max_response_bytes"
64
+ options.max_response_bytes = integer_option(key, value)
65
+ else
66
+ raise InvalidOptionsError.new("unknown option #{key}")
67
+ end
68
+ end
69
+
70
+ validate(options)
71
+ options
72
+ end
73
+
74
+ private def self.apply_headers(options : RequestOptions, value : JSON::Any) : Nil
75
+ headers = value.as_h?
76
+ unless headers
77
+ raise InvalidOptionsError.new("headers must be a JSON object")
78
+ end
79
+
80
+ headers.each do |name, header_value|
81
+ if name.empty?
82
+ raise InvalidOptionsError.new("header names must not be empty")
83
+ end
84
+
85
+ if text = header_value.as_s?
86
+ options.headers.add(name, text)
87
+ elsif values = header_value.as_a?
88
+ values.each do |item|
89
+ text = item.as_s?
90
+ unless text
91
+ raise InvalidOptionsError.new("header #{name} must be a string or an array of strings")
92
+ end
93
+ options.headers.add(name, text)
94
+ end
95
+ else
96
+ raise InvalidOptionsError.new("header #{name} must be a string or an array of strings")
97
+ end
98
+ end
99
+ end
100
+
101
+ private def self.apply_cookies(options : RequestOptions, value : JSON::Any) : Nil
102
+ cookies = value.as_h?
103
+ unless cookies
104
+ raise InvalidOptionsError.new("cookies must be a JSON object")
105
+ end
106
+
107
+ cookies.each do |name, cookie_value|
108
+ text = cookie_value.as_s?
109
+ unless text
110
+ raise InvalidOptionsError.new("cookie #{name} must be a string")
111
+ end
112
+
113
+ if name.empty?
114
+ raise InvalidOptionsError.new("cookie names must not be empty")
115
+ end
116
+
117
+ options.cookies[name] = text
118
+ end
119
+ end
120
+
121
+ private def self.string_option(key : String, value : JSON::Any) : String
122
+ value.as_s? || raise InvalidOptionsError.new("#{key} must be a string")
123
+ end
124
+
125
+ private def self.integer_option(key : String, value : JSON::Any) : Int64
126
+ value.as_i64? || raise InvalidOptionsError.new("#{key} must be an integer")
127
+ end
128
+
129
+ private def self.int32_option(key : String, value : JSON::Any) : Int32
130
+ integer = integer_option(key, value)
131
+ unless Int32::MIN <= integer <= Int32::MAX
132
+ raise InvalidOptionsError.new("#{key} is out of range")
133
+ end
134
+ integer.to_i32
135
+ end
136
+
137
+ private def self.seconds_option(
138
+ key : String,
139
+ value : JSON::Any,
140
+ allow_zero : Bool = false,
141
+ ) : Time::Span
142
+ number = if float = value.as_f?
143
+ float
144
+ elsif integer = value.as_i64?
145
+ integer.to_f
146
+ else
147
+ raise InvalidOptionsError.new("#{key} must be a number")
148
+ end
149
+
150
+ if allow_zero ? number < 0 : number <= 0
151
+ raise InvalidOptionsError.new("#{key} must be greater than zero")
152
+ end
153
+
154
+ number.seconds
155
+ end
156
+
157
+ private def self.validate(options : RequestOptions) : Nil
158
+ if options.user_agent.empty?
159
+ raise InvalidOptionsError.new("user_agent must not be empty")
160
+ end
161
+
162
+ if options.max_redirects < 0
163
+ raise InvalidOptionsError.new("max_redirects must not be negative")
164
+ end
165
+
166
+ if options.retries < 0
167
+ raise InvalidOptionsError.new("retries must not be negative")
168
+ end
169
+
170
+ if options.retries > 10
171
+ raise InvalidOptionsError.new("retries must not exceed 10")
172
+ end
173
+
174
+ if options.max_response_bytes <= 0
175
+ raise InvalidOptionsError.new("max_response_bytes must be positive")
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,70 @@
1
+ require "uri"
2
+
3
+ require "./errors"
4
+ require "./mime_type"
5
+
6
+ module Rill::SegmentAssembly
7
+ enum Family
8
+ MpegTs
9
+ FragmentedMp4
10
+ Webm
11
+ Adts
12
+ Mp3
13
+ end
14
+
15
+ FRAGMENTED_EXTENSIONS = {
16
+ "m4s", "mp4", "m4a", "m4v", "cmfv", "cmfa",
17
+ }
18
+
19
+ def self.for_hls(map_url : URI?, extensions : Array(String)) : Family
20
+ unique = extensions.map(&.downcase).uniq
21
+
22
+ if map_url
23
+ unless unique.all? { |extension| extension.empty? || FRAGMENTED_EXTENSIONS.includes?(extension) }
24
+ raise UnsupportedManifestError.new("HLS segments are not a supported fragmented MP4 layout")
25
+ end
26
+
27
+ return Family::FragmentedMp4
28
+ end
29
+
30
+ case unique
31
+ when ["ts"]
32
+ Family::MpegTs
33
+ when ["aac"]
34
+ Family::Adts
35
+ when ["mp3"]
36
+ Family::Mp3
37
+ else
38
+ raise UnsupportedManifestError.new(
39
+ "HLS segment concatenation is not verified for #{unique.inspect}",
40
+ )
41
+ end
42
+ end
43
+
44
+ def self.for_dash(mime : MimeType, has_init : Bool) : Family
45
+ case mime.subtype
46
+ when "mp4", "m4a", "m4v"
47
+ unless has_init
48
+ raise UnsupportedManifestError.new("DASH fragmented MP4 needs an initialization segment")
49
+ end
50
+
51
+ Family::FragmentedMp4
52
+ when "webm"
53
+ unless has_init
54
+ raise UnsupportedManifestError.new("DASH WebM needs an initialization segment")
55
+ end
56
+
57
+ Family::Webm
58
+ when "mp2t"
59
+ Family::MpegTs
60
+ when "aac"
61
+ Family::Adts
62
+ when "mpeg"
63
+ Family::Mp3
64
+ else
65
+ raise UnsupportedManifestError.new(
66
+ "DASH segment concatenation is not verified for #{mime.type}/#{mime.subtype}",
67
+ )
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ module Rill
2
+ module Timestamp
3
+ extend self
4
+
5
+ def clock_seconds(value : String?) : Float64?
6
+ return nil unless value
7
+
8
+ total = 0.0
9
+ value.split(':').each do |part|
10
+ number = part.to_f?
11
+ return nil unless number && number.finite? && number >= 0
12
+
13
+ total = total * 60 + number
14
+ end
15
+
16
+ total
17
+ end
18
+
19
+ def iso8601_seconds(value : String?) : Float64?
20
+ return nil unless value
21
+
22
+ match = /\AP(?:(\d+)D)?T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?\z/.match(value)
23
+ return nil unless match
24
+
25
+ days = match[1]?.try(&.to_f) || 0.0
26
+ hours = match[2]?.try(&.to_f) || 0.0
27
+ minutes = match[3]?.try(&.to_f) || 0.0
28
+ seconds = match[4]?.try(&.to_f) || 0.0
29
+
30
+ total = days * 86_400 + hours * 3600 + minutes * 60 + seconds
31
+ total if total > 0
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Rill
2
+ VERSION = "2026.11.14"
3
+ end
@@ -0,0 +1,47 @@
1
+ require "uri"
2
+
3
+ require "./rill/version"
4
+ require "./rill/errors"
5
+ require "./rill/request_options"
6
+ require "./rill/http_client"
7
+ require "./rill/format"
8
+ require "./rill/http_url"
9
+ require "./rill/json_value"
10
+ require "./rill/mime_type"
11
+ require "./rill/segment_assembly"
12
+ require "./rill/timestamp"
13
+ require "./rill/media"
14
+ require "./rill/extractor"
15
+ require "./rill/playlist"
16
+ require "./rill/extractors/bandcamp"
17
+ require "./rill/extractors/dash"
18
+ require "./rill/extractors/direct_media"
19
+ require "./rill/extractors/generic"
20
+ require "./rill/extractors/hls"
21
+ require "./rill/extractors/internet_archive"
22
+ require "./rill/extractors/m3u"
23
+ require "./rill/extractors/media_ccc_de"
24
+ require "./rill/extractors/mixcloud"
25
+ require "./rill/extractors/peer_tube"
26
+ require "./rill/extractors/rumble"
27
+ require "./rill/extractors/soundcloud"
28
+ require "./rill/extractors/spotify"
29
+ require "./rill/extractors/twitch_clip"
30
+ require "./rill/extractors/youtube"
31
+ require "./rill/extractor_registry"
32
+ require "./rill/format_selector"
33
+ require "./rill/cancellation"
34
+ require "./rill/cookie_jar"
35
+ require "./rill/download_progress"
36
+ require "./rill/output_file_name"
37
+ require "./rill/download_options"
38
+ require "./rill/download_plan"
39
+ require "./rill/downloader"
40
+ require "./rill/client"
41
+ require "./rill/media_muxer"
42
+
43
+ module Rill
44
+ def self.extract(url : String | URI, options : RequestOptions = RequestOptions.new) : Media | Playlist
45
+ ExtractorRegistry.default.extract(url, options)
46
+ end
47
+ end
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "rill-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2026.11.14",
4
+ "description": "Node.js bindings for the Rill media extractor and downloader",
5
+ "license": "Apache-2.0",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "gypfile": true,
9
+ "files": [
10
+ "index.js",
11
+ "index.d.ts",
12
+ "binding.gyp",
13
+ "src/**/*",
14
+ "scripts/**/*",
15
+ "native-src/**/*",
16
+ "native/**/*",
17
+ "README.md",
18
+ "LICENSE",
19
+ "NOTICE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/SimaoMoreira5228/rill.git",
27
+ "directory": "bindings/node"
28
+ },
29
+ "homepage": "https://github.com/SimaoMoreira5228/rill",
30
+ "bugs": {
31
+ "url": "https://github.com/SimaoMoreira5228/rill/issues"
32
+ },
33
+ "keywords": [
34
+ "rill",
35
+ "media",
36
+ "video",
37
+ "audio",
38
+ "download",
39
+ "extractor"
40
+ ],
41
+ "scripts": {
42
+ "prepack": "node scripts/prepack.js",
43
+ "install": "node scripts/install.js",
44
+ "package-native": "node scripts/package-native.js",
45
+ "test": "node smoke.js"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }
@@ -0,0 +1,46 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { spawnSync } = require("node:child_process");
4
+
5
+ const packageDir = path.resolve(__dirname, "..");
6
+ const nativeDir = path.join(packageDir, "native");
7
+ const libraryName =
8
+ process.platform === "darwin"
9
+ ? "librill.dylib"
10
+ : process.platform === "win32"
11
+ ? "rill.dll"
12
+ : "librill.so";
13
+ const library = path.join(nativeDir, libraryName);
14
+
15
+ if (fs.existsSync(library) || process.env.RILL_LIBRARY || process.env.RILL_LIB_DIR) {
16
+ process.exit(0);
17
+ }
18
+
19
+ const vendored = path.join(packageDir, "native-src");
20
+ const root = fs.existsSync(path.join(vendored, "src", "c_api.cr"))
21
+ ? vendored
22
+ : path.resolve(packageDir, "..", "..");
23
+ const source = path.join(root, "src", "c_api.cr");
24
+
25
+ if (!fs.existsSync(source)) {
26
+ console.error(`Rill source not found at ${source}`);
27
+ console.error("Set RILL_LIB_DIR or RILL_LIBRARY to an existing librill build.");
28
+ process.exit(1);
29
+ }
30
+
31
+ fs.mkdirSync(nativeDir, { recursive: true });
32
+
33
+ const linkFlag = process.platform === "win32" ? "/DLL" : "-shared";
34
+ const result = spawnSync(
35
+ process.env.CRYSTAL || "crystal",
36
+ ["build", "--single-module", "--link-flags", linkFlag, "-o", library, source],
37
+ { cwd: root, stdio: "inherit" },
38
+ );
39
+
40
+ if (result.error) {
41
+ console.error("Unable to run crystal. Install Crystal 1.22 or set RILL_LIB_DIR.");
42
+ console.error(result.error.message);
43
+ process.exit(1);
44
+ }
45
+
46
+ process.exit(result.status ?? 1);
@@ -0,0 +1,10 @@
1
+ const { spawnSync } = require("node:child_process");
2
+
3
+ const result = spawnSync("node-gyp", ["rebuild"], { stdio: "inherit" });
4
+
5
+ if (result.error && result.error.code === "ENOENT") {
6
+ const fallback = spawnSync("npx", ["--yes", "node-gyp", "rebuild"], { stdio: "inherit" });
7
+ process.exit(fallback.status ?? 1);
8
+ }
9
+
10
+ process.exit(result.status ?? 1);
@@ -0,0 +1,19 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { spawnSync } = require("node:child_process");
4
+ const { nativeDir } = require("./platform.js");
5
+
6
+ const packageDir = path.resolve(__dirname, "..");
7
+
8
+ if (fs.existsSync(path.join(nativeDir(packageDir), "rill_node.node"))) {
9
+ process.exit(0);
10
+ }
11
+
12
+ for (const script of ["build-native.js", "install-gyp.js"]) {
13
+ const result = spawnSync(process.execPath, [path.join(__dirname, script)], {
14
+ stdio: "inherit",
15
+ });
16
+ if (result.status !== 0) {
17
+ process.exit(result.status ?? 1);
18
+ }
19
+ }
package/scripts/lib.js ADDED
@@ -0,0 +1 @@
1
+ console.log(process.env.RILL_LIBRARY || "-lrill");
@@ -0,0 +1,23 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const packageDir = path.resolve(__dirname, "..");
5
+ const libraryName =
6
+ process.platform === "darwin"
7
+ ? "librill.dylib"
8
+ : process.platform === "win32"
9
+ ? "rill.dll"
10
+ : "librill.so";
11
+
12
+ if (process.env.RILL_LIBRARY) {
13
+ console.log(path.dirname(process.env.RILL_LIBRARY));
14
+ } else if (process.env.RILL_LIB_DIR) {
15
+ console.log(process.env.RILL_LIB_DIR);
16
+ } else {
17
+ const native = path.join(packageDir, "native");
18
+ if (fs.existsSync(path.join(native, libraryName))) {
19
+ console.log(native);
20
+ } else {
21
+ console.log(path.resolve(packageDir, "..", "..", "build"));
22
+ }
23
+ }
@@ -0,0 +1,35 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { helperName, libraryName, platformKey } = require("./platform.js");
4
+
5
+ const packageDir = path.resolve(__dirname, "..");
6
+ const root = path.resolve(packageDir, "..", "..");
7
+ const output = path.join(packageDir, "native", platformKey());
8
+
9
+ const library =
10
+ process.env.RILL_LIBRARY || path.join(root, "build", libraryName());
11
+ const helper = path.join(root, "build", helperName());
12
+ const addon = process.env.RILL_NODE_ADDON || path.join(packageDir, "build", "Release", "rill_node.node");
13
+
14
+ for (const [label, source] of [
15
+ ["librill", library],
16
+ ["rill-media", helper],
17
+ ["rill_node.node", addon],
18
+ ]) {
19
+ if (!fs.existsSync(source)) {
20
+ console.error(`Missing ${label}: ${source}`);
21
+ process.exit(1);
22
+ }
23
+ }
24
+
25
+ fs.mkdirSync(output, { recursive: true });
26
+ fs.copyFileSync(library, path.join(output, path.basename(library)));
27
+ fs.copyFileSync(helper, path.join(output, path.basename(helper)));
28
+ fs.copyFileSync(addon, path.join(output, "rill_node.node"));
29
+
30
+ if (process.platform !== "win32") {
31
+ fs.chmodSync(path.join(output, path.basename(library)), 0o755);
32
+ fs.chmodSync(path.join(output, path.basename(helper)), 0o755);
33
+ }
34
+
35
+ console.log(output);
@@ -0,0 +1,41 @@
1
+ const path = require("node:path");
2
+
3
+ function platformKey() {
4
+ if (process.env.RILL_PLATFORM_KEY) {
5
+ return process.env.RILL_PLATFORM_KEY;
6
+ }
7
+
8
+ const arch = process.arch;
9
+ if (process.platform === "linux") {
10
+ const report = process.report?.getReport?.();
11
+ const libc = report?.header?.glibcVersionRuntime ? "gnu" : "musl";
12
+ return `linux-${arch}-${libc}`;
13
+ }
14
+
15
+ return `${process.platform}-${arch}`;
16
+ }
17
+
18
+ function libraryName(platform = process.platform) {
19
+ if (platform === "darwin") {
20
+ return "librill.dylib";
21
+ }
22
+ if (platform === "win32") {
23
+ return "rill.dll";
24
+ }
25
+ return "librill.so";
26
+ }
27
+
28
+ function helperName(platform = process.platform) {
29
+ return platform === "win32" ? "rill-media.exe" : "rill-media";
30
+ }
31
+
32
+ function nativeDir(packageDir) {
33
+ return path.join(packageDir, "native", platformKey());
34
+ }
35
+
36
+ module.exports = {
37
+ helperName,
38
+ libraryName,
39
+ nativeDir,
40
+ platformKey,
41
+ };