@media-engine/providers 0.1.0 → 1.0.0

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 (214) hide show
  1. package/README.md +149 -242
  2. package/README.ru.md +204 -0
  3. package/dist/aniliberty-streaming/client.d.ts +33 -0
  4. package/dist/aniliberty-streaming/client.js +188 -0
  5. package/dist/aniliberty-streaming/client.js.map +1 -0
  6. package/dist/aniliberty-streaming/config.d.ts +24 -0
  7. package/dist/aniliberty-streaming/config.js +61 -0
  8. package/dist/aniliberty-streaming/config.js.map +1 -0
  9. package/dist/aniliberty-streaming/index.d.ts +4 -0
  10. package/dist/aniliberty-streaming/index.js +45 -0
  11. package/dist/aniliberty-streaming/index.js.map +1 -0
  12. package/dist/aniliberty-streaming/mapping.d.ts +3 -0
  13. package/dist/aniliberty-streaming/mapping.js +87 -0
  14. package/dist/aniliberty-streaming/mapping.js.map +1 -0
  15. package/dist/aniliberty-streaming/matching.d.ts +4 -0
  16. package/dist/aniliberty-streaming/matching.js +44 -0
  17. package/dist/aniliberty-streaming/matching.js.map +1 -0
  18. package/dist/anilist/graphql.d.ts +2 -0
  19. package/dist/anilist/graphql.js +135 -0
  20. package/dist/anilist/graphql.js.map +1 -0
  21. package/dist/anilist/index.js +11 -6
  22. package/dist/anilist/index.js.map +1 -1
  23. package/dist/bitsearch-torrent/client.d.ts +19 -0
  24. package/dist/bitsearch-torrent/client.js +181 -0
  25. package/dist/bitsearch-torrent/client.js.map +1 -0
  26. package/dist/bitsearch-torrent/config.d.ts +26 -0
  27. package/dist/bitsearch-torrent/config.js +74 -0
  28. package/dist/bitsearch-torrent/config.js.map +1 -0
  29. package/dist/bitsearch-torrent/index.d.ts +4 -0
  30. package/dist/bitsearch-torrent/index.js +35 -0
  31. package/dist/bitsearch-torrent/index.js.map +1 -0
  32. package/dist/bitsearch-torrent/mapping.d.ts +3 -0
  33. package/dist/bitsearch-torrent/mapping.js +53 -0
  34. package/dist/bitsearch-torrent/mapping.js.map +1 -0
  35. package/dist/bitsearch-torrent/matching.d.ts +3 -0
  36. package/dist/bitsearch-torrent/matching.js +13 -0
  37. package/dist/bitsearch-torrent/matching.js.map +1 -0
  38. package/dist/bitsearch-torrent/quota.d.ts +9 -0
  39. package/dist/bitsearch-torrent/quota.js +53 -0
  40. package/dist/bitsearch-torrent/quota.js.map +1 -0
  41. package/dist/cinemeta/index.js +53 -35
  42. package/dist/cinemeta/index.js.map +1 -1
  43. package/dist/ddbb-streaming/client.d.ts +24 -0
  44. package/dist/ddbb-streaming/client.js +98 -0
  45. package/dist/ddbb-streaming/client.js.map +1 -0
  46. package/dist/ddbb-streaming/config.d.ts +31 -0
  47. package/dist/ddbb-streaming/config.js +67 -0
  48. package/dist/ddbb-streaming/config.js.map +1 -0
  49. package/dist/ddbb-streaming/index.d.ts +4 -0
  50. package/dist/ddbb-streaming/index.js +53 -0
  51. package/dist/ddbb-streaming/index.js.map +1 -0
  52. package/dist/ddbb-streaming/mapping.d.ts +8 -0
  53. package/dist/ddbb-streaming/mapping.js +164 -0
  54. package/dist/ddbb-streaming/mapping.js.map +1 -0
  55. package/dist/ddbb-streaming/validation.d.ts +3 -0
  56. package/dist/ddbb-streaming/validation.js +84 -0
  57. package/dist/ddbb-streaming/validation.js.map +1 -0
  58. package/dist/experimental-streaming/index.js +55 -0
  59. package/dist/experimental-streaming/index.js.map +1 -1
  60. package/dist/flixhq-streaming/client.d.ts +12 -0
  61. package/dist/flixhq-streaming/client.js +103 -0
  62. package/dist/flixhq-streaming/client.js.map +1 -0
  63. package/dist/flixhq-streaming/index.d.ts +2 -2
  64. package/dist/flixhq-streaming/index.js +67 -106
  65. package/dist/flixhq-streaming/index.js.map +1 -1
  66. package/dist/imdb-dataset/index.d.ts +20 -5
  67. package/dist/imdb-dataset/index.js +83 -197
  68. package/dist/imdb-dataset/index.js.map +1 -1
  69. package/dist/imdb-dataset/memory-storage.d.ts +7 -0
  70. package/dist/imdb-dataset/memory-storage.js +168 -0
  71. package/dist/imdb-dataset/memory-storage.js.map +1 -0
  72. package/dist/imdb-dataset/sqlite-import.d.ts +24 -0
  73. package/dist/imdb-dataset/sqlite-import.js +298 -0
  74. package/dist/imdb-dataset/sqlite-import.js.map +1 -0
  75. package/dist/imdb-dataset/sqlite-runtime.d.ts +3 -0
  76. package/dist/imdb-dataset/sqlite-runtime.js +11 -0
  77. package/dist/imdb-dataset/sqlite-runtime.js.map +1 -0
  78. package/dist/imdb-dataset/sqlite-schema.d.ts +6 -0
  79. package/dist/imdb-dataset/sqlite-schema.js +88 -0
  80. package/dist/imdb-dataset/sqlite-schema.js.map +1 -0
  81. package/dist/imdb-dataset/sqlite-storage.d.ts +12 -0
  82. package/dist/imdb-dataset/sqlite-storage.js +208 -0
  83. package/dist/imdb-dataset/sqlite-storage.js.map +1 -0
  84. package/dist/imdb-dataset/storage.d.ts +33 -0
  85. package/dist/imdb-dataset/storage.js +2 -0
  86. package/dist/imdb-dataset/storage.js.map +1 -0
  87. package/dist/index.d.ts +7 -0
  88. package/dist/index.js +7 -0
  89. package/dist/index.js.map +1 -1
  90. package/dist/jacred-torrent/client.d.ts +24 -0
  91. package/dist/jacred-torrent/client.js +254 -0
  92. package/dist/jacred-torrent/client.js.map +1 -0
  93. package/dist/jacred-torrent/config.d.ts +24 -0
  94. package/dist/jacred-torrent/config.js +70 -0
  95. package/dist/jacred-torrent/config.js.map +1 -0
  96. package/dist/jacred-torrent/index.d.ts +4 -0
  97. package/dist/jacred-torrent/index.js +45 -0
  98. package/dist/jacred-torrent/index.js.map +1 -0
  99. package/dist/jacred-torrent/mapping.d.ts +3 -0
  100. package/dist/jacred-torrent/mapping.js +129 -0
  101. package/dist/jacred-torrent/mapping.js.map +1 -0
  102. package/dist/jacred-torrent/matching.d.ts +3 -0
  103. package/dist/jacred-torrent/matching.js +19 -0
  104. package/dist/jacred-torrent/matching.js.map +1 -0
  105. package/dist/kinobd/index.js +15 -33
  106. package/dist/kinobd/index.js.map +1 -1
  107. package/dist/kinobd-streaming/availability.d.ts +3 -0
  108. package/dist/kinobd-streaming/availability.js +179 -0
  109. package/dist/kinobd-streaming/availability.js.map +1 -0
  110. package/dist/kinobd-streaming/candidates.d.ts +32 -0
  111. package/dist/kinobd-streaming/candidates.js +282 -0
  112. package/dist/kinobd-streaming/candidates.js.map +1 -0
  113. package/dist/kinobd-streaming/config.d.ts +64 -0
  114. package/dist/kinobd-streaming/config.js +129 -0
  115. package/dist/kinobd-streaming/config.js.map +1 -0
  116. package/dist/kinobd-streaming/index.d.ts +3 -30
  117. package/dist/kinobd-streaming/index.js +2 -904
  118. package/dist/kinobd-streaming/index.js.map +1 -1
  119. package/dist/kinobd-streaming/players.d.ts +24 -0
  120. package/dist/kinobd-streaming/players.js +270 -0
  121. package/dist/kinobd-streaming/players.js.map +1 -0
  122. package/dist/kinobd-streaming/request-budget.d.ts +15 -0
  123. package/dist/kinobd-streaming/request-budget.js +61 -0
  124. package/dist/kinobd-streaming/request-budget.js.map +1 -0
  125. package/dist/kinobd-streaming/validation.d.ts +11 -0
  126. package/dist/kinobd-streaming/validation.js +170 -0
  127. package/dist/kinobd-streaming/validation.js.map +1 -0
  128. package/dist/magnetz-torrent/client.d.ts +16 -0
  129. package/dist/magnetz-torrent/client.js +235 -0
  130. package/dist/magnetz-torrent/client.js.map +1 -0
  131. package/dist/magnetz-torrent/config.d.ts +27 -0
  132. package/dist/magnetz-torrent/config.js +73 -0
  133. package/dist/magnetz-torrent/config.js.map +1 -0
  134. package/dist/magnetz-torrent/index.d.ts +4 -0
  135. package/dist/magnetz-torrent/index.js +35 -0
  136. package/dist/magnetz-torrent/index.js.map +1 -0
  137. package/dist/magnetz-torrent/mapping.d.ts +3 -0
  138. package/dist/magnetz-torrent/mapping.js +52 -0
  139. package/dist/magnetz-torrent/mapping.js.map +1 -0
  140. package/dist/magnetz-torrent/matching.d.ts +3 -0
  141. package/dist/magnetz-torrent/matching.js +5 -0
  142. package/dist/magnetz-torrent/matching.js.map +1 -0
  143. package/dist/magnetz-torrent/request-gate.d.ts +9 -0
  144. package/dist/magnetz-torrent/request-gate.js +48 -0
  145. package/dist/magnetz-torrent/request-gate.js.map +1 -0
  146. package/dist/package-version.d.ts +2 -0
  147. package/dist/package-version.js +5 -0
  148. package/dist/package-version.js.map +1 -0
  149. package/dist/shared/abort.d.ts +3 -0
  150. package/dist/shared/abort.js +30 -0
  151. package/dist/shared/abort.js.map +1 -0
  152. package/dist/shared/http.d.ts +13 -2
  153. package/dist/shared/http.js +77 -75
  154. package/dist/shared/http.js.map +1 -1
  155. package/dist/shared/index.d.ts +2 -0
  156. package/dist/shared/index.js +2 -0
  157. package/dist/shared/index.js.map +1 -1
  158. package/dist/shared/mapping.d.ts +5 -0
  159. package/dist/shared/mapping.js +32 -0
  160. package/dist/shared/mapping.js.map +1 -0
  161. package/dist/shared/options.d.ts +1 -0
  162. package/dist/shared/options.js +10 -0
  163. package/dist/shared/options.js.map +1 -0
  164. package/dist/shared/output-url.d.ts +2 -0
  165. package/dist/shared/output-url.js +50 -0
  166. package/dist/shared/output-url.js.map +1 -0
  167. package/dist/shared/rate-limit.d.ts +11 -0
  168. package/dist/shared/rate-limit.js +52 -0
  169. package/dist/shared/rate-limit.js.map +1 -0
  170. package/dist/shared/response-body.d.ts +6 -0
  171. package/dist/shared/response-body.js +109 -0
  172. package/dist/shared/response-body.js.map +1 -0
  173. package/dist/shared/retry.d.ts +10 -0
  174. package/dist/shared/retry.js +31 -0
  175. package/dist/shared/retry.js.map +1 -0
  176. package/dist/shared/safe-fetch.d.ts +14 -0
  177. package/dist/shared/safe-fetch.js +274 -0
  178. package/dist/shared/safe-fetch.js.map +1 -0
  179. package/dist/shared/torrent-release-matching.d.ts +7 -0
  180. package/dist/shared/torrent-release-matching.js +143 -0
  181. package/dist/shared/torrent-release-matching.js.map +1 -0
  182. package/dist/shared/torrent-release-metadata.d.ts +4 -0
  183. package/dist/shared/torrent-release-metadata.js +95 -0
  184. package/dist/shared/torrent-release-metadata.js.map +1 -0
  185. package/dist/shikimori/index.js +19 -11
  186. package/dist/shikimori/index.js.map +1 -1
  187. package/dist/tvmaze/index.d.ts +11 -0
  188. package/dist/tvmaze/index.js +274 -0
  189. package/dist/tvmaze/index.js.map +1 -0
  190. package/dist/wikidata/cache.d.ts +11 -0
  191. package/dist/wikidata/cache.js +30 -0
  192. package/dist/wikidata/cache.js.map +1 -0
  193. package/dist/wikidata/candidates.d.ts +12 -0
  194. package/dist/wikidata/candidates.js +67 -0
  195. package/dist/wikidata/candidates.js.map +1 -0
  196. package/dist/wikidata/client.d.ts +40 -0
  197. package/dist/wikidata/client.js +224 -0
  198. package/dist/wikidata/client.js.map +1 -0
  199. package/dist/wikidata/index.d.ts +3 -0
  200. package/dist/wikidata/index.js +34 -111
  201. package/dist/wikidata/index.js.map +1 -1
  202. package/dist/yts-torrent/client.d.ts +25 -0
  203. package/dist/yts-torrent/client.js +163 -0
  204. package/dist/yts-torrent/client.js.map +1 -0
  205. package/dist/yts-torrent/config.d.ts +22 -0
  206. package/dist/yts-torrent/config.js +57 -0
  207. package/dist/yts-torrent/config.js.map +1 -0
  208. package/dist/yts-torrent/index.d.ts +4 -0
  209. package/dist/yts-torrent/index.js +42 -0
  210. package/dist/yts-torrent/index.js.map +1 -0
  211. package/dist/yts-torrent/mapping.d.ts +4 -0
  212. package/dist/yts-torrent/mapping.js +107 -0
  213. package/dist/yts-torrent/mapping.js.map +1 -0
  214. package/package.json +23 -6
@@ -0,0 +1,298 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { mkdir, open, rename, stat, unlink } from "node:fs/promises";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import { createInterface } from "node:readline";
5
+ import { randomUUID } from "node:crypto";
6
+ import { createGunzip } from "node:zlib";
7
+ import { addAbortSignal } from "node:stream";
8
+ import { normalizeProviderSearchText } from "../shared/mapping.js";
9
+ import { createImdbSqliteDatabase } from "./sqlite-runtime.js";
10
+ import { createImdbDatasetSqliteSchema, IMDB_DATASET_SQLITE_SCHEMA_VERSION, validateImdbDatasetSqliteIntegrity, validateImdbDatasetSqliteSchema, } from "./sqlite-schema.js";
11
+ const PROGRESS_INTERVAL_ROWS = 50_000;
12
+ const REQUIRED_BASICS_COLUMNS = [
13
+ "tconst",
14
+ "titleType",
15
+ "primaryTitle",
16
+ "originalTitle",
17
+ "isAdult",
18
+ "startYear",
19
+ "endYear",
20
+ "runtimeMinutes",
21
+ "genres",
22
+ ];
23
+ const REQUIRED_RATINGS_COLUMNS = ["tconst", "averageRating", "numVotes"];
24
+ // Streams official plain or gzip TSV files into a versioned index and atomically publishes it.
25
+ // Потоково импортирует plain или gzip TSV в versioned index и атомарно публикует его.
26
+ export async function buildImdbDatasetSqliteIndex(options) {
27
+ const startedAt = performance.now();
28
+ const titleBasicsPath = resolveRequiredPath(options.titleBasicsPath, "titleBasicsPath");
29
+ const titleRatingsPath = options.titleRatingsPath
30
+ ? resolveRequiredPath(options.titleRatingsPath, "titleRatingsPath")
31
+ : undefined;
32
+ const outputPath = resolveRequiredPath(options.outputPath, "outputPath");
33
+ if (outputPath === titleBasicsPath || outputPath === titleRatingsPath) {
34
+ throw new TypeError("IMDb SQLite outputPath must differ from every input path");
35
+ }
36
+ options.signal?.throwIfAborted();
37
+ await mkdir(dirname(outputPath), { recursive: true });
38
+ const temporaryPath = resolve(dirname(outputPath), `.${basename(outputPath)}.${process.pid}.${randomUUID()}.tmp`);
39
+ let database;
40
+ try {
41
+ database = await createImdbSqliteDatabase(temporaryPath, {
42
+ enableForeignKeyConstraints: true,
43
+ enableDoubleQuotedStringLiterals: false,
44
+ allowExtension: false,
45
+ });
46
+ createImdbDatasetSqliteSchema(database);
47
+ database.exec("BEGIN IMMEDIATE");
48
+ let titles;
49
+ let ratings = { processed: 0, imported: 0 };
50
+ try {
51
+ titles = await importTitles(database, titleBasicsPath, options);
52
+ if (titleRatingsPath) {
53
+ ratings = await importRatings(database, titleRatingsPath, options);
54
+ }
55
+ database.prepare("UPDATE metadata SET value = 'ready' WHERE key = 'state'").run();
56
+ database
57
+ .prepare("INSERT INTO metadata(key, value) VALUES ('title_rows', ?)")
58
+ .run(String(titles.imported));
59
+ database
60
+ .prepare("INSERT INTO metadata(key, value) VALUES ('rating_rows', ?)")
61
+ .run(String(ratings.imported));
62
+ database.exec("COMMIT");
63
+ }
64
+ catch (error) {
65
+ database.exec("ROLLBACK");
66
+ throw error;
67
+ }
68
+ options.signal?.throwIfAborted();
69
+ emitProgress(options, "finalize", titles.processed, titles.imported);
70
+ database.exec("INSERT INTO title_search(title_search) VALUES ('optimize'); ANALYZE; PRAGMA optimize; VACUUM;");
71
+ validateImdbDatasetSqliteSchema(database);
72
+ validateImdbDatasetSqliteIntegrity(database);
73
+ database.close();
74
+ database = undefined;
75
+ await syncFile(temporaryPath);
76
+ options.signal?.throwIfAborted();
77
+ await rename(temporaryPath, outputPath);
78
+ await syncDirectory(dirname(outputPath));
79
+ const index = await stat(outputPath);
80
+ return {
81
+ outputPath,
82
+ schemaVersion: IMDB_DATASET_SQLITE_SCHEMA_VERSION,
83
+ titleRows: titles.imported,
84
+ ratingRows: ratings.imported,
85
+ skippedTitleRows: titles.processed - titles.imported,
86
+ indexBytes: index.size,
87
+ durationMs: performance.now() - startedAt,
88
+ };
89
+ }
90
+ catch (error) {
91
+ if (database) {
92
+ try {
93
+ database.close();
94
+ }
95
+ catch {
96
+ // Preserve the original import error.
97
+ }
98
+ }
99
+ await cleanupTemporaryDatabase(temporaryPath);
100
+ throw error;
101
+ }
102
+ }
103
+ async function importTitles(database, path, options) {
104
+ const insertTitle = database.prepare(`
105
+ INSERT OR IGNORE INTO titles (
106
+ imdb_id,
107
+ media_type,
108
+ primary_title,
109
+ original_title,
110
+ start_year,
111
+ end_year,
112
+ runtime_minutes,
113
+ genres,
114
+ normalized_primary_title,
115
+ normalized_original_title
116
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
117
+ `);
118
+ const insertSearch = database.prepare(`
119
+ INSERT INTO title_search(rowid, normalized_primary_title, normalized_original_title)
120
+ VALUES (?, ?, ?)
121
+ `);
122
+ const counts = { processed: 0, imported: 0 };
123
+ await forEachTsvRow(path, REQUIRED_BASICS_COLUMNS, options.signal, (values, columns) => {
124
+ counts.processed += 1;
125
+ checkProgress(options, "titles", counts);
126
+ const imdbId = readValue(values, columns.tconst);
127
+ const mediaType = mapMediaType(readValue(values, columns.titleType));
128
+ const primaryTitle = emptyToUndefined(readValue(values, columns.primaryTitle));
129
+ if (!/^tt\d{7,12}$/.test(imdbId) ||
130
+ !mediaType ||
131
+ !primaryTitle ||
132
+ (!options.includeAdult && readValue(values, columns.isAdult) === "1")) {
133
+ return;
134
+ }
135
+ const originalTitle = emptyToUndefined(readValue(values, columns.originalTitle));
136
+ const normalizedPrimaryTitle = normalizeProviderSearchText(primaryTitle);
137
+ const normalizedOriginalTitle = normalizeProviderSearchText(originalTitle ?? "");
138
+ if (!normalizedPrimaryTitle) {
139
+ return;
140
+ }
141
+ const insertion = insertTitle.run(imdbId, mediaType, primaryTitle, originalTitle ?? null, parseNumber(readValue(values, columns.startYear)) ?? null, parseNumber(readValue(values, columns.endYear)) ?? null, parseNumber(readValue(values, columns.runtimeMinutes)) ?? null, emptyToUndefined(readValue(values, columns.genres)) ?? null, normalizedPrimaryTitle, normalizedOriginalTitle);
142
+ if (Number(insertion.changes) === 0) {
143
+ return;
144
+ }
145
+ insertSearch.run(insertion.lastInsertRowid, normalizedPrimaryTitle, normalizedOriginalTitle === normalizedPrimaryTitle ? "" : normalizedOriginalTitle);
146
+ counts.imported += 1;
147
+ });
148
+ emitProgress(options, "titles", counts.processed, counts.imported);
149
+ return counts;
150
+ }
151
+ async function importRatings(database, path, options) {
152
+ const insertRating = database.prepare(`
153
+ INSERT OR REPLACE INTO ratings(imdb_id, average_rating, num_votes)
154
+ SELECT ?, ?, ?
155
+ WHERE EXISTS (SELECT 1 FROM titles WHERE imdb_id = ?)
156
+ `);
157
+ const counts = { processed: 0, imported: 0 };
158
+ await forEachTsvRow(path, REQUIRED_RATINGS_COLUMNS, options.signal, (values, columns) => {
159
+ counts.processed += 1;
160
+ checkProgress(options, "ratings", counts);
161
+ const imdbId = readValue(values, columns.tconst);
162
+ const averageRating = parseNumber(readValue(values, columns.averageRating));
163
+ if (!/^tt\d{7,12}$/.test(imdbId) || averageRating === undefined) {
164
+ return;
165
+ }
166
+ const insertion = insertRating.run(imdbId, averageRating, parseNumber(readValue(values, columns.numVotes)) ?? null, imdbId);
167
+ counts.imported += Number(insertion.changes);
168
+ });
169
+ emitProgress(options, "ratings", counts.processed, counts.imported);
170
+ return counts;
171
+ }
172
+ async function forEachTsvRow(path, requiredColumns, signal, visit) {
173
+ signal?.throwIfAborted();
174
+ const file = createReadStream(path, { signal });
175
+ let input = file;
176
+ let forwardFileError;
177
+ if (path.toLowerCase().endsWith(".gz")) {
178
+ const gunzip = createGunzip();
179
+ forwardFileError = (error) => gunzip.destroy(error);
180
+ file.once("error", forwardFileError);
181
+ input = file.pipe(gunzip);
182
+ if (signal) {
183
+ addAbortSignal(signal, input);
184
+ }
185
+ }
186
+ const lines = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
187
+ let columns;
188
+ try {
189
+ for await (const rawLine of lines) {
190
+ signal?.throwIfAborted();
191
+ const line = rawLine.replace(/\r$/, "");
192
+ if (!columns) {
193
+ columns = resolveColumns(line.replace(/^\uFEFF/, ""), requiredColumns, path);
194
+ continue;
195
+ }
196
+ if (line) {
197
+ visit(line.split("\t"), columns);
198
+ }
199
+ }
200
+ }
201
+ finally {
202
+ lines.close();
203
+ input.destroy();
204
+ file.destroy();
205
+ if (forwardFileError) {
206
+ file.off("error", forwardFileError);
207
+ }
208
+ }
209
+ if (!columns) {
210
+ throw new Error(`IMDb TSV file is empty: ${path}`);
211
+ }
212
+ }
213
+ function resolveColumns(headerLine, requiredColumns, path) {
214
+ const headers = headerLine.split("\t");
215
+ const columns = {};
216
+ for (const name of requiredColumns) {
217
+ const index = headers.indexOf(name);
218
+ if (index < 0) {
219
+ throw new Error(`IMDb TSV file ${path} is missing required column ${name}`);
220
+ }
221
+ columns[name] = index;
222
+ }
223
+ return columns;
224
+ }
225
+ function checkProgress(options, phase, counts) {
226
+ if (counts.processed % 4096 === 0) {
227
+ options.signal?.throwIfAborted();
228
+ }
229
+ if (counts.processed % PROGRESS_INTERVAL_ROWS === 0) {
230
+ emitProgress(options, phase, counts.processed, counts.imported);
231
+ }
232
+ }
233
+ function emitProgress(options, phase, processedRows, importedRows) {
234
+ options.signal?.throwIfAborted();
235
+ options.onProgress?.({ phase, processedRows, importedRows });
236
+ }
237
+ function readValue(values, index) {
238
+ return values[index] ?? "";
239
+ }
240
+ function mapMediaType(value) {
241
+ if (value === "movie") {
242
+ return "movie";
243
+ }
244
+ if (value === "tvSeries") {
245
+ return "series";
246
+ }
247
+ return undefined;
248
+ }
249
+ function parseNumber(value) {
250
+ const normalized = emptyToUndefined(value);
251
+ if (!normalized) {
252
+ return undefined;
253
+ }
254
+ const parsed = Number(normalized);
255
+ return Number.isFinite(parsed) ? parsed : undefined;
256
+ }
257
+ function emptyToUndefined(value) {
258
+ return value && value !== "\\N" ? value : undefined;
259
+ }
260
+ function resolveRequiredPath(value, name) {
261
+ if (typeof value !== "string" || !value.trim()) {
262
+ throw new TypeError(`${name} must be a non-empty string`);
263
+ }
264
+ return resolve(value);
265
+ }
266
+ async function syncFile(path) {
267
+ const file = await open(path, "r");
268
+ try {
269
+ await file.sync();
270
+ }
271
+ finally {
272
+ await file.close();
273
+ }
274
+ }
275
+ async function syncDirectory(path) {
276
+ try {
277
+ await syncFile(path);
278
+ }
279
+ catch (error) {
280
+ const code = error.code;
281
+ if (code !== "EINVAL" && code !== "EPERM" && code !== "EISDIR") {
282
+ throw error;
283
+ }
284
+ }
285
+ }
286
+ async function cleanupTemporaryDatabase(path) {
287
+ await Promise.all([path, `${path}-journal`, `${path}-wal`, `${path}-shm`].map(async (candidate) => {
288
+ try {
289
+ await unlink(candidate);
290
+ }
291
+ catch (error) {
292
+ if (error.code !== "ENOENT") {
293
+ throw error;
294
+ }
295
+ }
296
+ }));
297
+ }
298
+ //# sourceMappingURL=sqlite-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-import.js","sourceRoot":"","sources":["../../src/imdb-dataset/sqlite-import.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ export type ImdbSqliteDatabase = DatabaseSync;
3
+ export declare function createImdbSqliteDatabase(path: string, options?: ConstructorParameters<typeof DatabaseSync>[1]): Promise<DatabaseSync>;
@@ -0,0 +1,11 @@
1
+ export async function createImdbSqliteDatabase(path, options) {
2
+ let DatabaseSyncConstructor;
3
+ try {
4
+ ({ DatabaseSync: DatabaseSyncConstructor } = await import("node:sqlite"));
5
+ }
6
+ catch (error) {
7
+ throw new Error("The persisted IMDb SQLite backend requires Node.js 22.13 or newer with node:sqlite support", { cause: error });
8
+ }
9
+ return new DatabaseSyncConstructor(path, options);
10
+ }
11
+ //# sourceMappingURL=sqlite-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-runtime.js","sourceRoot":"","sources":["../../src/imdb-dataset/sqlite-runtime.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,IAAY,EACZ,OAAuD;IAEvD,IAAI,uBAA4C,CAAC;IAEjD,IAAI,CAAC;QACH,CAAC,EAAE,YAAY,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,4FAA4F,EAC5F,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACpD,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ export declare const IMDB_DATASET_SQLITE_SCHEMA_VERSION = 1;
3
+ export declare const IMDB_DATASET_SQLITE_APPLICATION_ID = 1296385097;
4
+ export declare function createImdbDatasetSqliteSchema(database: DatabaseSync): void;
5
+ export declare function validateImdbDatasetSqliteSchema(database: DatabaseSync): void;
6
+ export declare function validateImdbDatasetSqliteIntegrity(database: DatabaseSync): void;
@@ -0,0 +1,88 @@
1
+ export const IMDB_DATASET_SQLITE_SCHEMA_VERSION = 1;
2
+ export const IMDB_DATASET_SQLITE_APPLICATION_ID = 0x4d454449;
3
+ const SCHEMA_SQL = `
4
+ PRAGMA page_size = 4096;
5
+ PRAGMA journal_mode = OFF;
6
+ PRAGMA synchronous = OFF;
7
+ PRAGMA temp_store = FILE;
8
+ PRAGMA cache_size = -16384;
9
+ PRAGMA locking_mode = EXCLUSIVE;
10
+ PRAGMA application_id = ${IMDB_DATASET_SQLITE_APPLICATION_ID};
11
+ PRAGMA user_version = ${IMDB_DATASET_SQLITE_SCHEMA_VERSION};
12
+
13
+ CREATE TABLE metadata (
14
+ key TEXT PRIMARY KEY NOT NULL,
15
+ value TEXT NOT NULL
16
+ ) WITHOUT ROWID;
17
+
18
+ CREATE TABLE titles (
19
+ id INTEGER PRIMARY KEY,
20
+ imdb_id TEXT NOT NULL UNIQUE,
21
+ media_type TEXT NOT NULL CHECK (media_type IN ('movie', 'series')),
22
+ primary_title TEXT NOT NULL,
23
+ original_title TEXT,
24
+ start_year INTEGER,
25
+ end_year INTEGER,
26
+ runtime_minutes INTEGER,
27
+ genres TEXT,
28
+ normalized_primary_title TEXT NOT NULL,
29
+ normalized_original_title TEXT NOT NULL
30
+ );
31
+
32
+ CREATE INDEX titles_primary_title_idx
33
+ ON titles(normalized_primary_title, media_type, start_year DESC);
34
+ CREATE INDEX titles_original_title_idx
35
+ ON titles(normalized_original_title, media_type, start_year DESC);
36
+
37
+ CREATE TABLE ratings (
38
+ imdb_id TEXT PRIMARY KEY NOT NULL REFERENCES titles(imdb_id) ON DELETE CASCADE,
39
+ average_rating REAL NOT NULL,
40
+ num_votes INTEGER
41
+ ) WITHOUT ROWID;
42
+
43
+ CREATE VIRTUAL TABLE title_search USING fts5(
44
+ normalized_primary_title,
45
+ normalized_original_title,
46
+ content='',
47
+ tokenize='trigram remove_diacritics 1'
48
+ );
49
+
50
+ INSERT INTO metadata(key, value) VALUES
51
+ ('state', 'building'),
52
+ ('schema_version', '${IMDB_DATASET_SQLITE_SCHEMA_VERSION}');
53
+ `;
54
+ export function createImdbDatasetSqliteSchema(database) {
55
+ try {
56
+ database.exec(SCHEMA_SQL);
57
+ }
58
+ catch (error) {
59
+ throw new Error("Unable to create the IMDb SQLite index; Node.js SQLite must include FTS5 trigram support", { cause: error });
60
+ }
61
+ }
62
+ export function validateImdbDatasetSqliteSchema(database) {
63
+ const applicationId = readPragmaNumber(database, "application_id");
64
+ const schemaVersion = readPragmaNumber(database, "user_version");
65
+ const state = database.prepare("SELECT value FROM metadata WHERE key = 'state'").get()?.value;
66
+ if (applicationId !== IMDB_DATASET_SQLITE_APPLICATION_ID) {
67
+ throw new Error("File is not a Media Engine IMDb SQLite index");
68
+ }
69
+ if (schemaVersion !== IMDB_DATASET_SQLITE_SCHEMA_VERSION) {
70
+ throw new Error(`Unsupported IMDb SQLite schema version ${schemaVersion}; expected ${IMDB_DATASET_SQLITE_SCHEMA_VERSION}`);
71
+ }
72
+ if (state !== "ready") {
73
+ throw new Error("IMDb SQLite index is incomplete");
74
+ }
75
+ }
76
+ export function validateImdbDatasetSqliteIntegrity(database) {
77
+ const result = database.prepare("PRAGMA quick_check(1)").get();
78
+ const status = result ? Object.values(result)[0] : undefined;
79
+ if (status !== "ok") {
80
+ throw new Error(`IMDb SQLite index integrity check failed: ${String(status)}`);
81
+ }
82
+ }
83
+ function readPragmaNumber(database, name) {
84
+ const result = database.prepare(`PRAGMA ${name}`).get();
85
+ const value = result?.[name];
86
+ return typeof value === "number" ? value : Number.NaN;
87
+ }
88
+ //# sourceMappingURL=sqlite-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-schema.js","sourceRoot":"","sources":["../../src/imdb-dataset/sqlite-schema.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,kCAAkC,GAAG,UAAU,CAAC;AAE7D,MAAM,UAAU,GAAG;;;;;;;4BAOS,kCAAkC;0BACpC,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAyClC,kCAAkC;CAC3D,CAAC;AAEF,MAAM,UAAU,6BAA6B,CAAC,QAAsB;IAClE,IAAI,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,0FAA0F,EAC1F,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,QAAsB;IACpE,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnE,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC;IAE9F,IAAI,aAAa,KAAK,kCAAkC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,aAAa,KAAK,kCAAkC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,0CAA0C,aAAa,cAAc,kCAAkC,EAAE,CAC1G,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,QAAsB;IACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,GAAG,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE7D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,6CAA6C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAsB,EAAE,IAAuC;IACvF,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;IAE7B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;AACxD,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ImdbDatasetStorage } from "./storage.js";
2
+ export declare const IMDB_DATASET_SQLITE_SCHEMA_VERSION = 1;
3
+ export interface ImdbDatasetSqliteStorageOptions {
4
+ path: string;
5
+ busyTimeoutMs?: number;
6
+ }
7
+ export interface ImdbDatasetSqliteStorage extends ImdbDatasetStorage {
8
+ readonly path: string;
9
+ readonly schemaVersion: number;
10
+ close(): void;
11
+ }
12
+ export declare function openImdbDatasetSqliteStorage(options: ImdbDatasetSqliteStorageOptions): Promise<ImdbDatasetSqliteStorage>;
@@ -0,0 +1,208 @@
1
+ import { resolve } from "node:path";
2
+ import { createImdbSqliteDatabase } from "./sqlite-runtime.js";
3
+ import { IMDB_DATASET_SQLITE_SCHEMA_VERSION as SQLITE_SCHEMA_VERSION, validateImdbDatasetSqliteSchema, } from "./sqlite-schema.js";
4
+ export const IMDB_DATASET_SQLITE_SCHEMA_VERSION = SQLITE_SCHEMA_VERSION;
5
+ const DEFAULT_BUSY_TIMEOUT_MS = 1_000;
6
+ const MAX_BUSY_TIMEOUT_MS = 60_000;
7
+ const SELECT_COLUMNS = `
8
+ t.imdb_id,
9
+ t.media_type,
10
+ t.primary_title,
11
+ t.original_title,
12
+ t.start_year,
13
+ t.end_year,
14
+ t.runtime_minutes,
15
+ t.genres,
16
+ t.normalized_primary_title,
17
+ t.normalized_original_title,
18
+ r.average_rating,
19
+ r.num_votes
20
+ `;
21
+ const LOOKUP_SQL = `
22
+ SELECT ${SELECT_COLUMNS}
23
+ FROM titles AS t
24
+ LEFT JOIN ratings AS r ON r.imdb_id = t.imdb_id
25
+ WHERE t.imdb_id = ?
26
+ `;
27
+ const SEARCH_SQL = `
28
+ SELECT ${SELECT_COLUMNS}
29
+ FROM title_search
30
+ JOIN titles AS t ON t.id = title_search.rowid
31
+ LEFT JOIN ratings AS r ON r.imdb_id = t.imdb_id
32
+ WHERE title_search MATCH ?
33
+ AND (? IS NULL OR t.media_type = ?)
34
+ AND (? IS NULL OR t.start_year = ?)
35
+ ORDER BY
36
+ CASE
37
+ WHEN t.normalized_primary_title = ? OR t.normalized_original_title = ? THEN 0
38
+ ELSE 1
39
+ END,
40
+ COALESCE(t.start_year, 0) DESC,
41
+ t.imdb_id ASC
42
+ LIMIT ?
43
+ `;
44
+ const PREFIX_SEARCH_SQL = `
45
+ SELECT ${SELECT_COLUMNS}
46
+ FROM titles AS t
47
+ LEFT JOIN ratings AS r ON r.imdb_id = t.imdb_id
48
+ WHERE (
49
+ t.normalized_primary_title GLOB ?
50
+ OR t.normalized_original_title GLOB ?
51
+ )
52
+ AND (? IS NULL OR t.media_type = ?)
53
+ AND (? IS NULL OR t.start_year = ?)
54
+ ORDER BY
55
+ CASE
56
+ WHEN t.normalized_primary_title = ? OR t.normalized_original_title = ? THEN 0
57
+ ELSE 1
58
+ END,
59
+ COALESCE(t.start_year, 0) DESC,
60
+ t.imdb_id ASC
61
+ LIMIT ?
62
+ `;
63
+ // Opens a read-only persisted IMDb index without affecting the legacy TSV path.
64
+ // Открывает read-only постоянный IMDb index, не затрагивая прежний TSV-путь.
65
+ export async function openImdbDatasetSqliteStorage(options) {
66
+ const path = resolveRequiredPath(options.path, "IMDb SQLite index path");
67
+ const timeout = resolveBusyTimeout(options.busyTimeoutMs);
68
+ const database = await createImdbSqliteDatabase(path, {
69
+ readOnly: true,
70
+ enableForeignKeyConstraints: true,
71
+ enableDoubleQuotedStringLiterals: false,
72
+ allowExtension: false,
73
+ });
74
+ try {
75
+ validateImdbDatasetSqliteSchema(database);
76
+ database.exec(`PRAGMA query_only = ON; PRAGMA busy_timeout = ${timeout}; PRAGMA cache_size = -8192; PRAGMA trusted_schema = OFF;`);
77
+ return new SqliteStorage(database, path);
78
+ }
79
+ catch (error) {
80
+ database.close();
81
+ throw error;
82
+ }
83
+ }
84
+ class SqliteStorage {
85
+ schemaVersion = SQLITE_SCHEMA_VERSION;
86
+ path;
87
+ database;
88
+ lookupStatement;
89
+ searchStatement;
90
+ prefixSearchStatement;
91
+ closed = false;
92
+ constructor(database, path) {
93
+ this.database = database;
94
+ this.path = path;
95
+ this.lookupStatement = database.prepare(LOOKUP_SQL);
96
+ this.searchStatement = database.prepare(SEARCH_SQL);
97
+ this.prefixSearchStatement = database.prepare(PREFIX_SEARCH_SQL);
98
+ }
99
+ getTitleById(imdbId, options) {
100
+ this.assertOpen();
101
+ options?.signal?.throwIfAborted();
102
+ const row = this.lookupStatement.get(imdbId);
103
+ options?.signal?.throwIfAborted();
104
+ return row ? mapRow(row) : undefined;
105
+ }
106
+ searchTitles(query) {
107
+ this.assertOpen();
108
+ query.signal?.throwIfAborted();
109
+ const prefixRows = this.prefixSearchStatement.all(`${query.normalizedTitle}*`, `${query.normalizedTitle}*`, query.type ?? null, query.type ?? null, query.year ?? null, query.year ?? null, query.normalizedTitle, query.normalizedTitle, query.limit);
110
+ const hasExactMatch = prefixRows.some((row) => isExactRow(row, query.normalizedTitle));
111
+ let rows = prefixRows;
112
+ if (!hasExactMatch &&
113
+ prefixRows.length < query.limit &&
114
+ [...query.normalizedTitle].length >= 3) {
115
+ const substringRows = this.searchStatement.all(quoteFtsPhrase(query.normalizedTitle), query.type ?? null, query.type ?? null, query.year ?? null, query.year ?? null, query.normalizedTitle, query.normalizedTitle, query.limit);
116
+ rows = mergeSearchRows(prefixRows, substringRows, query.limit);
117
+ }
118
+ query.signal?.throwIfAborted();
119
+ return rows.map((row) => ({
120
+ record: mapRow(row),
121
+ confidence: isExactRow(row, query.normalizedTitle) ? 1 : 0.75,
122
+ }));
123
+ }
124
+ close() {
125
+ if (this.closed) {
126
+ return;
127
+ }
128
+ this.database.close();
129
+ this.closed = true;
130
+ }
131
+ assertOpen() {
132
+ if (this.closed) {
133
+ throw new Error("IMDb SQLite storage is closed");
134
+ }
135
+ }
136
+ }
137
+ function mergeSearchRows(prefixRows, substringRows, limit) {
138
+ const merged = new Map();
139
+ for (const row of [...prefixRows, ...substringRows]) {
140
+ const imdbId = readRequiredString(row.imdb_id, "imdb_id");
141
+ if (!merged.has(imdbId)) {
142
+ merged.set(imdbId, row);
143
+ }
144
+ }
145
+ return [...merged.values()]
146
+ .sort((left, right) => (readOptionalNumber(right.start_year) ?? 0) - (readOptionalNumber(left.start_year) ?? 0) ||
147
+ readRequiredString(left.imdb_id, "imdb_id").localeCompare(readRequiredString(right.imdb_id, "imdb_id")))
148
+ .slice(0, limit);
149
+ }
150
+ function isExactRow(row, normalizedTitle) {
151
+ return (row.normalized_primary_title === normalizedTitle ||
152
+ row.normalized_original_title === normalizedTitle);
153
+ }
154
+ function mapRow(row) {
155
+ const imdbId = readRequiredString(row.imdb_id, "imdb_id");
156
+ const mediaType = readRequiredString(row.media_type, "media_type");
157
+ const primaryTitle = readRequiredString(row.primary_title, "primary_title");
158
+ if (mediaType !== "movie" && mediaType !== "series") {
159
+ throw new Error(`Invalid IMDb SQLite media type for ${imdbId}`);
160
+ }
161
+ const rating = readOptionalNumber(row.average_rating);
162
+ const genres = readOptionalString(row.genres);
163
+ return {
164
+ imdbId,
165
+ type: mediaType,
166
+ primaryTitle,
167
+ originalTitle: readOptionalString(row.original_title),
168
+ startYear: readOptionalNumber(row.start_year),
169
+ endYear: readOptionalNumber(row.end_year),
170
+ runtimeMinutes: readOptionalNumber(row.runtime_minutes),
171
+ genres: genres?.split(",").filter(Boolean),
172
+ rating: rating === undefined
173
+ ? undefined
174
+ : {
175
+ averageRating: rating,
176
+ numVotes: readOptionalNumber(row.num_votes),
177
+ },
178
+ };
179
+ }
180
+ function quoteFtsPhrase(value) {
181
+ return `"${value.replaceAll('"', '""')}"`;
182
+ }
183
+ function readRequiredString(value, column) {
184
+ if (typeof value !== "string" || !value) {
185
+ throw new Error(`Invalid IMDb SQLite ${column} value`);
186
+ }
187
+ return value;
188
+ }
189
+ function readOptionalString(value) {
190
+ return typeof value === "string" && value ? value : undefined;
191
+ }
192
+ function readOptionalNumber(value) {
193
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
194
+ }
195
+ function resolveRequiredPath(value, name) {
196
+ if (typeof value !== "string" || !value.trim()) {
197
+ throw new TypeError(`${name} must be a non-empty string`);
198
+ }
199
+ return resolve(value);
200
+ }
201
+ function resolveBusyTimeout(value) {
202
+ const resolved = value ?? DEFAULT_BUSY_TIMEOUT_MS;
203
+ if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > MAX_BUSY_TIMEOUT_MS) {
204
+ throw new RangeError(`IMDb SQLite busyTimeoutMs must be between 0 and ${MAX_BUSY_TIMEOUT_MS}`);
205
+ }
206
+ return resolved;
207
+ }
208
+ //# sourceMappingURL=sqlite-storage.js.map