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,336 @@
1
+ require "./status"
2
+ require "./memory"
3
+ require "./sync"
4
+
5
+ lib LibRillJob
6
+ struct Job
7
+ next_job : Job*
8
+ kind : Int32
9
+ url : UInt8*
10
+ output_path : UInt8*
11
+ options_json : UInt8*
12
+ json : UInt8*
13
+ error_message : UInt8*
14
+ error_code : UInt8*
15
+ status : Int32
16
+ done : Int32
17
+ abandoned : Int32
18
+ download_state : Int32
19
+ downloaded : Int64
20
+ total : Int64
21
+ cancel_requested : Int32
22
+ mutex : Rill::CAPI::Sync::Mutex*
23
+ cond : Rill::CAPI::Sync::Cond*
24
+ end
25
+ end
26
+
27
+ module Rill::CAPI
28
+ alias JobPtr = Pointer(LibRillJob::Job)
29
+ end
30
+
31
+ module Rill::CAPI::Job
32
+ extend self
33
+
34
+ def create_extraction(url : UInt8*, options_json : UInt8*) : JobPtr
35
+ job = allocate
36
+ return JobPtr.null if job.null?
37
+
38
+ job.value.kind = Rill::CAPI::Kind::EXTRACTION
39
+
40
+ if url.null?
41
+ finish(
42
+ job,
43
+ Status::INVALID_ARGUMENT,
44
+ null_pointer,
45
+ Memory.copy_string("URL is null"),
46
+ Memory.copy_string("invalid_argument"),
47
+ )
48
+ return job
49
+ end
50
+
51
+ job.value.url = Memory.copy_c_string(url)
52
+ unless options_json.null?
53
+ job.value.options_json = Memory.copy_c_string(options_json)
54
+ end
55
+
56
+ if job.value.url.null?
57
+ finish(job, Status::INTERNAL_ERROR, null_pointer, null_pointer, null_pointer)
58
+ end
59
+
60
+ job
61
+ end
62
+
63
+ def create_download(url : UInt8*, output_path : UInt8*, options_json : UInt8*) : JobPtr
64
+ job = allocate
65
+ return JobPtr.null if job.null?
66
+
67
+ job.value.kind = Rill::CAPI::Kind::DOWNLOAD
68
+ job.value.download_state = Rill::CAPI::DownloadState::QUEUED
69
+
70
+ if url.null? || output_path.null?
71
+ message = url.null? ? "URL is null" : "output path is null"
72
+ finish_download(
73
+ job,
74
+ Status::INVALID_ARGUMENT,
75
+ Rill::CAPI::DownloadState::FAILED,
76
+ Memory.copy_string(message),
77
+ Memory.copy_string("invalid_argument"),
78
+ )
79
+ return job
80
+ end
81
+
82
+ job.value.url = Memory.copy_c_string(url)
83
+ job.value.output_path = Memory.copy_c_string(output_path)
84
+ unless options_json.null?
85
+ job.value.options_json = Memory.copy_c_string(options_json)
86
+ end
87
+
88
+ if job.value.url.null? || job.value.output_path.null?
89
+ finish_download(
90
+ job,
91
+ Status::INTERNAL_ERROR,
92
+ Rill::CAPI::DownloadState::FAILED,
93
+ null_pointer,
94
+ null_pointer,
95
+ )
96
+ end
97
+
98
+ job
99
+ end
100
+
101
+ def done?(job : JobPtr) : Bool
102
+ job.value.done != 0
103
+ end
104
+
105
+ def finish(
106
+ job : JobPtr,
107
+ status : Int32,
108
+ json : UInt8*,
109
+ error_message : UInt8*,
110
+ error_code : UInt8*,
111
+ ) : Nil
112
+ Sync.lock(job.value.mutex)
113
+ job.value.status = status
114
+ job.value.json = json
115
+ job.value.error_message = error_message
116
+ job.value.error_code = error_code
117
+ job.value.done = 1
118
+ Sync.signal(job.value.cond)
119
+ abandoned = job.value.abandoned != 0
120
+ Sync.unlock(job.value.mutex)
121
+
122
+ release(job) if abandoned
123
+ end
124
+
125
+ def finish_download(
126
+ job : JobPtr,
127
+ status : Int32,
128
+ state : Int32,
129
+ error_message : UInt8*,
130
+ error_code : UInt8*,
131
+ ) : Nil
132
+ Sync.lock(job.value.mutex)
133
+ job.value.status = status
134
+ job.value.download_state = state
135
+ job.value.error_message = error_message
136
+ job.value.error_code = error_code
137
+ job.value.done = 1
138
+ Sync.signal(job.value.cond)
139
+ abandoned = job.value.abandoned != 0
140
+ Sync.unlock(job.value.mutex)
141
+
142
+ release(job) if abandoned
143
+ end
144
+
145
+ def mark_running(job : JobPtr) : Nil
146
+ Sync.lock(job.value.mutex)
147
+ if job.value.done == 0
148
+ job.value.download_state = Rill::CAPI::DownloadState::RUNNING
149
+ end
150
+ Sync.unlock(job.value.mutex)
151
+ end
152
+
153
+ def update_progress(job : JobPtr, downloaded : Int64, total : Int64?) : Nil
154
+ Sync.lock(job.value.mutex)
155
+ if job.value.done == 0
156
+ job.value.downloaded = downloaded
157
+ job.value.total = total if total
158
+ end
159
+ Sync.unlock(job.value.mutex)
160
+ end
161
+
162
+ def cancel(job : JobPtr) : Int32
163
+ return Status::INVALID_ARGUMENT if job.null?
164
+
165
+ Sync.lock(job.value.mutex)
166
+ if job.value.done == 0
167
+ job.value.cancel_requested = 1
168
+ end
169
+ Sync.unlock(job.value.mutex)
170
+
171
+ Status::OK
172
+ end
173
+
174
+ def poll(
175
+ job : JobPtr,
176
+ state_out : Int32*,
177
+ downloaded_out : Int64*,
178
+ total_out : Int64*,
179
+ ) : Int32
180
+ return Status::INVALID_ARGUMENT if job.null?
181
+
182
+ Sync.lock(job.value.mutex)
183
+ state = job.value.download_state
184
+ downloaded = job.value.downloaded
185
+ total = job.value.total
186
+ Sync.unlock(job.value.mutex)
187
+
188
+ state_out.value = state unless state_out.null?
189
+ downloaded_out.value = downloaded unless downloaded_out.null?
190
+ total_out.value = total unless total_out.null?
191
+
192
+ Status::OK
193
+ end
194
+
195
+ def error_message(job : JobPtr) : UInt8*
196
+ return null_pointer if job.null?
197
+
198
+ Sync.lock(job.value.mutex)
199
+ pointer = if job.value.error_message.null?
200
+ null_pointer
201
+ else
202
+ Memory.copy_string(String.new(job.value.error_message))
203
+ end
204
+ Sync.unlock(job.value.mutex)
205
+
206
+ pointer
207
+ end
208
+
209
+ def error_code(job : JobPtr) : UInt8*
210
+ return null_pointer if job.null?
211
+
212
+ Sync.lock(job.value.mutex)
213
+ pointer = if job.value.error_code.null?
214
+ null_pointer
215
+ else
216
+ Memory.copy_string(String.new(job.value.error_code))
217
+ end
218
+ Sync.unlock(job.value.mutex)
219
+
220
+ pointer
221
+ end
222
+
223
+ def wait(
224
+ job : JobPtr,
225
+ json_out : UInt8**,
226
+ error_out : UInt8**,
227
+ error_code_out : UInt8**,
228
+ ) : Int32
229
+ return Status::INVALID_ARGUMENT if job.null?
230
+
231
+ Sync.lock(job.value.mutex)
232
+ while job.value.done == 0
233
+ Sync.wait(job.value.cond, job.value.mutex)
234
+ end
235
+
236
+ status = job.value.status
237
+
238
+ unless json_out.null?
239
+ json_out.value = job.value.json
240
+ job.value.json = null_pointer
241
+ end
242
+
243
+ unless error_out.null?
244
+ error_out.value = job.value.error_message
245
+ job.value.error_message = null_pointer
246
+ end
247
+
248
+ unless error_code_out.null?
249
+ error_code_out.value = job.value.error_code
250
+ job.value.error_code = null_pointer
251
+ end
252
+
253
+ Sync.unlock(job.value.mutex)
254
+ status
255
+ end
256
+
257
+ def free(job : JobPtr) : Nil
258
+ return if job.null?
259
+
260
+ Sync.lock(job.value.mutex)
261
+ unless job.value.done != 0
262
+ job.value.abandoned = 1
263
+ Sync.unlock(job.value.mutex)
264
+ return
265
+ end
266
+ Sync.unlock(job.value.mutex)
267
+
268
+ release(job)
269
+ end
270
+
271
+ private def allocate : JobPtr
272
+ job = LibC.malloc(sizeof(LibRillJob::Job)).as(JobPtr)
273
+ return JobPtr.null if job.null?
274
+
275
+ initialize_fields(job)
276
+
277
+ unless initialize_sync(job)
278
+ LibC.free(job)
279
+ return JobPtr.null
280
+ end
281
+
282
+ job
283
+ end
284
+
285
+ private def release(job : JobPtr) : Nil
286
+ Memory.free(job.value.url)
287
+ Memory.free(job.value.output_path)
288
+ Memory.free(job.value.options_json)
289
+ Memory.free(job.value.json)
290
+ Memory.free(job.value.error_message)
291
+ Memory.free(job.value.error_code)
292
+
293
+ Sync.destroy_cond(job.value.cond)
294
+ Sync.destroy_mutex(job.value.mutex)
295
+ LibC.free(job)
296
+ end
297
+
298
+ private def initialize_fields(job : JobPtr) : Nil
299
+ job.value.next_job = JobPtr.null
300
+ job.value.kind = Rill::CAPI::Kind::EXTRACTION
301
+ job.value.url = null_pointer
302
+ job.value.output_path = null_pointer
303
+ job.value.options_json = null_pointer
304
+ job.value.json = null_pointer
305
+ job.value.error_message = null_pointer
306
+ job.value.error_code = null_pointer
307
+ job.value.status = Status::OK
308
+ job.value.done = 0
309
+ job.value.abandoned = 0
310
+ job.value.download_state = Rill::CAPI::DownloadState::QUEUED
311
+ job.value.downloaded = 0_i64
312
+ job.value.total = 0_i64
313
+ job.value.cancel_requested = 0
314
+ job.value.mutex = Pointer(Sync::Mutex).null
315
+ job.value.cond = Pointer(Sync::Cond).null
316
+ end
317
+
318
+ private def initialize_sync(job : JobPtr) : Bool
319
+ mutex = Sync.create_mutex
320
+ return false if mutex.null?
321
+
322
+ cond = Sync.create_cond
323
+ if cond.null?
324
+ Sync.destroy_mutex(mutex)
325
+ return false
326
+ end
327
+
328
+ job.value.mutex = mutex
329
+ job.value.cond = cond
330
+ true
331
+ end
332
+
333
+ private def null_pointer : UInt8*
334
+ Pointer(UInt8).null
335
+ end
336
+ end
@@ -0,0 +1,32 @@
1
+ require "c/stdlib"
2
+
3
+ module Rill::CAPI::Memory
4
+ extend self
5
+
6
+ def copy_string(value : String) : UInt8*
7
+ size = value.bytesize + 1
8
+ pointer = LibC.malloc(size).as(UInt8*)
9
+ return Pointer(UInt8).null if pointer.null?
10
+
11
+ pointer.copy_from(value.to_unsafe, value.bytesize)
12
+ pointer[value.bytesize] = 0_u8
13
+ pointer
14
+ end
15
+
16
+ def copy_c_string(value : UInt8*) : UInt8*
17
+ size = LibC.strlen(value).to_i32 + 1
18
+ copy_bytes(value, size)
19
+ end
20
+
21
+ def copy_bytes(source : UInt8*, size : Int32) : UInt8*
22
+ pointer = LibC.malloc(size).as(UInt8*)
23
+ return Pointer(UInt8).null if pointer.null?
24
+
25
+ pointer.copy_from(source, size)
26
+ pointer
27
+ end
28
+
29
+ def free(value : UInt8*) : Nil
30
+ LibC.free(value) unless value.null?
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module Rill::CAPI
2
+ ABI_VERSION = 2
3
+
4
+ module Status
5
+ OK = 0
6
+ EXTRACTION_ERROR = 1
7
+ INTERNAL_ERROR = 2
8
+ INVALID_ARGUMENT = 3
9
+ SHUTDOWN = 4
10
+ CANCELLED = 5
11
+ end
12
+
13
+ module Kind
14
+ EXTRACTION = 0
15
+ DOWNLOAD = 1
16
+ end
17
+
18
+ module DownloadState
19
+ QUEUED = 0
20
+ RUNNING = 1
21
+ COMPLETED = 2
22
+ FAILED = 3
23
+ CANCELLED = 4
24
+ end
25
+ end
@@ -0,0 +1,125 @@
1
+ {% if flag?(:windows) %}
2
+ require "c/synchapi"
3
+ {% else %}
4
+ require "c/pthread"
5
+ {% end %}
6
+
7
+ require "c/stdlib"
8
+
9
+ module Rill::CAPI::Sync
10
+ extend self
11
+
12
+ {% if flag?(:windows) %}
13
+ alias Mutex = LibC::CRITICAL_SECTION
14
+ alias Cond = LibC::CONDITION_VARIABLE
15
+
16
+ def create_mutex : Mutex*
17
+ pointer = LibC.malloc(sizeof(Mutex)).as(Mutex*)
18
+ return Pointer(Mutex).null if pointer.null?
19
+
20
+ status = LibC.InitializeCriticalSectionAndSpinCount(pointer, 4000)
21
+ if status == 0
22
+ LibC.free(pointer)
23
+ return Pointer(Mutex).null
24
+ end
25
+
26
+ pointer
27
+ end
28
+
29
+ def create_cond : Cond*
30
+ pointer = LibC.malloc(sizeof(Cond)).as(Cond*)
31
+ return Pointer(Cond).null if pointer.null?
32
+
33
+ LibC.InitializeConditionVariable(pointer)
34
+ pointer
35
+ end
36
+
37
+ def destroy_mutex(mutex : Mutex*) : Nil
38
+ LibC.DeleteCriticalSection(mutex)
39
+ LibC.free(mutex)
40
+ end
41
+
42
+ def destroy_cond(cond : Cond*) : Nil
43
+ LibC.free(cond)
44
+ end
45
+
46
+ def lock(mutex : Mutex*) : Nil
47
+ LibC.EnterCriticalSection(mutex)
48
+ end
49
+
50
+ def unlock(mutex : Mutex*) : Nil
51
+ LibC.LeaveCriticalSection(mutex)
52
+ end
53
+
54
+ def wait(cond : Cond*, mutex : Mutex*) : Nil
55
+ LibC.SleepConditionVariableCS(cond, mutex, LibC::INFINITE)
56
+ end
57
+
58
+ def signal(cond : Cond*) : Nil
59
+ LibC.WakeConditionVariable(cond)
60
+ end
61
+
62
+ def broadcast(cond : Cond*) : Nil
63
+ LibC.WakeAllConditionVariable(cond)
64
+ end
65
+ {% else %}
66
+ alias Mutex = LibC::PthreadMutexT
67
+ alias Cond = LibC::PthreadCondT
68
+
69
+ def create_mutex : Mutex*
70
+ pointer = LibC.malloc(sizeof(Mutex)).as(Mutex*)
71
+ return Pointer(Mutex).null if pointer.null?
72
+
73
+ status = LibC.pthread_mutex_init(pointer, Pointer(LibC::PthreadMutexattrT).null)
74
+ if status != 0
75
+ LibC.free(pointer)
76
+ return Pointer(Mutex).null
77
+ end
78
+
79
+ pointer
80
+ end
81
+
82
+ def create_cond : Cond*
83
+ pointer = LibC.malloc(sizeof(Cond)).as(Cond*)
84
+ return Pointer(Cond).null if pointer.null?
85
+
86
+ status = LibC.pthread_cond_init(pointer, Pointer(LibC::PthreadCondattrT).null)
87
+ if status != 0
88
+ LibC.free(pointer)
89
+ return Pointer(Cond).null
90
+ end
91
+
92
+ pointer
93
+ end
94
+
95
+ def destroy_mutex(mutex : Mutex*) : Nil
96
+ LibC.pthread_mutex_destroy(mutex)
97
+ LibC.free(mutex)
98
+ end
99
+
100
+ def destroy_cond(cond : Cond*) : Nil
101
+ LibC.pthread_cond_destroy(cond)
102
+ LibC.free(cond)
103
+ end
104
+
105
+ def lock(mutex : Mutex*) : Nil
106
+ LibC.pthread_mutex_lock(mutex)
107
+ end
108
+
109
+ def unlock(mutex : Mutex*) : Nil
110
+ LibC.pthread_mutex_unlock(mutex)
111
+ end
112
+
113
+ def wait(cond : Cond*, mutex : Mutex*) : Nil
114
+ LibC.pthread_cond_wait(cond, mutex)
115
+ end
116
+
117
+ def signal(cond : Cond*) : Nil
118
+ LibC.pthread_cond_signal(cond)
119
+ end
120
+
121
+ def broadcast(cond : Cond*) : Nil
122
+ LibC.pthread_cond_broadcast(cond)
123
+ end
124
+ {% end %}
125
+ end