@fdm-monster/server 2.0.11 → 2.1.1

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 (244) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/.yarn/releases/{yarn-4.13.0.cjs → yarn-4.14.1.cjs} +288 -288
  3. package/.yarnrc.yml +5 -1
  4. package/README.md +2 -1
  5. package/RELEASE_NOTES.MD +153 -2
  6. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorate.js +1 -1
  7. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.129.0}/helpers/decorateMetadata.js +1 -1
  8. package/dist/_virtual/_virtual_controllers.js +2 -0
  9. package/dist/consoles/typeorm-create.js.map +1 -1
  10. package/dist/consoles/typeorm-generate.js.map +1 -1
  11. package/dist/consoles/typeorm-migrate.js.map +1 -1
  12. package/dist/constants/authorization.constants.js.map +1 -1
  13. package/dist/container.js +2 -0
  14. package/dist/container.js.map +1 -1
  15. package/dist/container.tokens.js +1 -0
  16. package/dist/container.tokens.js.map +1 -1
  17. package/dist/controllers/api-key.controller.js +72 -0
  18. package/dist/controllers/api-key.controller.js.map +1 -0
  19. package/dist/controllers/auth.controller.js +6 -4
  20. package/dist/controllers/auth.controller.js.map +1 -1
  21. package/dist/controllers/batch-call.controller.js +2 -2
  22. package/dist/controllers/batch-call.controller.js.map +1 -1
  23. package/dist/controllers/camera-stream.controller.js +2 -2
  24. package/dist/controllers/camera-stream.controller.js.map +1 -1
  25. package/dist/controllers/file-storage.controller.js +2 -2
  26. package/dist/controllers/file-storage.controller.js.map +1 -1
  27. package/dist/controllers/first-time-setup.controller.js +2 -2
  28. package/dist/controllers/first-time-setup.controller.js.map +1 -1
  29. package/dist/controllers/floor.controller.js +2 -2
  30. package/dist/controllers/floor.controller.js.map +1 -1
  31. package/dist/controllers/metrics.controller.js +2 -2
  32. package/dist/controllers/metrics.controller.js.map +1 -1
  33. package/dist/controllers/print-job.controller.js +2 -2
  34. package/dist/controllers/print-job.controller.js.map +1 -1
  35. package/dist/controllers/print-queue.controller.js +2 -2
  36. package/dist/controllers/print-queue.controller.js.map +1 -1
  37. package/dist/controllers/printer-files.controller.js +2 -2
  38. package/dist/controllers/printer-files.controller.js.map +1 -1
  39. package/dist/controllers/printer-maintenance-log.controller.js +2 -2
  40. package/dist/controllers/printer-maintenance-log.controller.js.map +1 -1
  41. package/dist/controllers/printer-settings.controller.js +2 -2
  42. package/dist/controllers/printer-settings.controller.js.map +1 -1
  43. package/dist/controllers/printer-tag.controller.js +2 -2
  44. package/dist/controllers/printer-tag.controller.js.map +1 -1
  45. package/dist/controllers/printer.controller.js +2 -2
  46. package/dist/controllers/printer.controller.js.map +1 -1
  47. package/dist/controllers/server-private.controller.js +2 -2
  48. package/dist/controllers/server-private.controller.js.map +1 -1
  49. package/dist/controllers/server-public.controller.js +2 -2
  50. package/dist/controllers/server-public.controller.js.map +1 -1
  51. package/dist/controllers/settings.controller.js +2 -2
  52. package/dist/controllers/settings.controller.js.map +1 -1
  53. package/dist/controllers/slicer-compat.controller.js +2 -2
  54. package/dist/controllers/slicer-compat.controller.js.map +1 -1
  55. package/dist/controllers/user.controller.js +2 -2
  56. package/dist/controllers/user.controller.js.map +1 -1
  57. package/dist/controllers/validation/api-key-controller.validation.js +11 -0
  58. package/dist/controllers/validation/api-key-controller.validation.js.map +1 -0
  59. package/dist/data-source.js +6 -2
  60. package/dist/data-source.js.map +1 -1
  61. package/dist/entities/api-key.entity.js +60 -0
  62. package/dist/entities/api-key.entity.js.map +1 -0
  63. package/dist/entities/camera-stream.entity.js +2 -2
  64. package/dist/entities/floor-position.entity.js +2 -2
  65. package/dist/entities/floor.entity.js +2 -2
  66. package/dist/entities/index.js +2 -1
  67. package/dist/entities/print-job.entity.js +2 -2
  68. package/dist/entities/printer-maintenance-log.entity.js +2 -2
  69. package/dist/entities/printer-tag.entity.js +2 -2
  70. package/dist/entities/printer.entity.js +2 -2
  71. package/dist/entities/refresh-token.entity.js +2 -2
  72. package/dist/entities/role.entity.js +2 -2
  73. package/dist/entities/settings.entity.js +2 -2
  74. package/dist/entities/tag.entity.js +2 -2
  75. package/dist/entities/user-role.entity.js +2 -2
  76. package/dist/entities/user.entity.js +2 -2
  77. package/dist/exceptions/failed-dependency.exception.js.map +1 -1
  78. package/dist/exceptions/job.exceptions.js.map +1 -1
  79. package/dist/exceptions/runtime.exceptions.js.map +1 -1
  80. package/dist/handlers/event-emitter.js.map +1 -1
  81. package/dist/handlers/logger-factory.js.map +1 -1
  82. package/dist/handlers/logger.js.map +1 -1
  83. package/dist/handlers/logging/file-logging.transport.js +1 -2
  84. package/dist/handlers/logging/file-logging.transport.js.map +1 -1
  85. package/dist/handlers/logging/loki-logging.transport.js.map +1 -1
  86. package/dist/handlers/logging/static.logger.js.map +1 -1
  87. package/dist/handlers/validators.js.map +1 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/middleware/api-key.strategy.js +45 -0
  90. package/dist/middleware/api-key.strategy.js.map +1 -0
  91. package/dist/middleware/authenticate.js.map +1 -1
  92. package/dist/middleware/database.js.map +1 -1
  93. package/dist/middleware/demo.middleware.js.map +1 -1
  94. package/dist/middleware/exception.filter.js.map +1 -1
  95. package/dist/middleware/global.middleware.js.map +1 -1
  96. package/dist/middleware/param-converter.middleware.js.map +1 -1
  97. package/dist/middleware/passport.js +3 -0
  98. package/dist/middleware/passport.js.map +1 -1
  99. package/dist/middleware/printer-resolver.js.map +1 -1
  100. package/dist/middleware/printer.js.map +1 -1
  101. package/dist/middleware/slicer-api-key.middleware.js.map +1 -1
  102. package/dist/middleware/socketio.middleware.js.map +1 -1
  103. package/dist/migrations/1706829146617-InitSqlite.js.map +1 -1
  104. package/dist/migrations/1707494762198-PrinterGroup.js.map +1 -1
  105. package/dist/migrations/1708465930665-ChangePrintCompletionDeletePrinterCascade.js.map +1 -1
  106. package/dist/migrations/1713300747465-ChangeRoleNameUnique.js.map +1 -1
  107. package/dist/migrations/1713897879622-AddPrinterType.js.map +1 -1
  108. package/dist/migrations/1720338804844-RemovePrinterFile.js.map +1 -1
  109. package/dist/migrations/1745141688926-AddPrinterUsernamePassword.js.map +1 -1
  110. package/dist/migrations/1766576698569-DropPermissions.js.map +1 -1
  111. package/dist/migrations/1767278216516-ChangeCameraPrinterOnDeleteSetNull.js.map +1 -1
  112. package/dist/migrations/1767279607392-DropCustomGcode.js.map +1 -1
  113. package/dist/migrations/1767291804417-DropPrintCompletions.js.map +1 -1
  114. package/dist/migrations/1767352862576-DropSettingsFileClean.js.map +1 -1
  115. package/dist/migrations/1767355639023-ChangeFloorLevelToOrder.js.map +1 -1
  116. package/dist/migrations/1767370191762-ChangeFloorNonUniqueOrder.js.map +1 -1
  117. package/dist/migrations/1767432108916-RenameGroupToTag.js.map +1 -1
  118. package/dist/migrations/1767451444137-AddPrintJob.js.map +1 -1
  119. package/dist/migrations/1767909428129-AddPrinterMaintenanceLog.js.map +1 -1
  120. package/dist/migrations/1778446203015-AddApiKey.js +49 -0
  121. package/dist/migrations/1778446203015-AddApiKey.js.map +1 -0
  122. package/dist/plugins/controllers-plugin.js.map +1 -1
  123. package/dist/server.constants.js +2 -1
  124. package/dist/server.constants.js.map +1 -1
  125. package/dist/server.core.js +6 -2
  126. package/dist/server.core.js.map +1 -1
  127. package/dist/server.env.js.map +1 -1
  128. package/dist/server.host.js.map +1 -1
  129. package/dist/services/authentication/auth.service.js.map +1 -1
  130. package/dist/services/authentication/jwt.service.js.map +1 -1
  131. package/dist/services/bambu/bambu-ftp.adapter.js.map +1 -1
  132. package/dist/services/bambu/bambu-mqtt.adapter.js.map +1 -1
  133. package/dist/services/bambu/bambu.client.js.map +1 -1
  134. package/dist/services/bambu.api.js.map +1 -1
  135. package/dist/services/core/batch-call.service.js.map +1 -1
  136. package/dist/services/core/client-bundle.service.js.map +1 -1
  137. package/dist/services/core/config.service.js +4 -0
  138. package/dist/services/core/config.service.js.map +1 -1
  139. package/dist/services/core/cradle.service.js.map +1 -1
  140. package/dist/services/core/github.service.js.map +1 -1
  141. package/dist/services/core/http-client.factory.js.map +1 -1
  142. package/dist/services/core/logs-manager.service.js.map +1 -1
  143. package/dist/services/core/monsterpi.service.js.map +1 -1
  144. package/dist/services/core/multer.service.js.map +1 -1
  145. package/dist/services/core/server-release.service.js.map +1 -1
  146. package/dist/services/core/yaml.service.js.map +1 -1
  147. package/dist/services/file-analysis.service.js.map +1 -1
  148. package/dist/services/file-storage.service.js.map +1 -1
  149. package/dist/services/interfaces/api-key.dto.js +19 -0
  150. package/dist/services/interfaces/api-key.dto.js.map +1 -0
  151. package/dist/services/interfaces/api-key.service.interface.js +1 -0
  152. package/dist/services/interfaces/user.dto.js +2 -0
  153. package/dist/services/interfaces/user.dto.js.map +1 -1
  154. package/dist/services/moonraker/moonraker-websocket.adapter.js.map +1 -1
  155. package/dist/services/moonraker/moonraker.client.js.map +1 -1
  156. package/dist/services/moonraker.api.js.map +1 -1
  157. package/dist/services/octoprint/octoprint-api.routes.js.map +1 -1
  158. package/dist/services/octoprint/octoprint-websocket.adapter.js.map +1 -1
  159. package/dist/services/octoprint/octoprint.client.js.map +1 -1
  160. package/dist/services/octoprint/utils/api.utils.js.map +1 -1
  161. package/dist/services/octoprint/utils/file.utils.js.map +1 -1
  162. package/dist/services/octoprint/utils/octoprint-http-client.builder.js.map +1 -1
  163. package/dist/services/octoprint.api.js.map +1 -1
  164. package/dist/services/orm/api-key.service.js +90 -0
  165. package/dist/services/orm/api-key.service.js.map +1 -0
  166. package/dist/services/orm/base.service.js.map +1 -1
  167. package/dist/services/orm/camera-stream.service.js.map +1 -1
  168. package/dist/services/orm/floor-position.service.js.map +1 -1
  169. package/dist/services/orm/floor.service.js.map +1 -1
  170. package/dist/services/orm/permission.service.js.map +1 -1
  171. package/dist/services/orm/print-job.service.js.map +1 -1
  172. package/dist/services/orm/printer-maintenance-log.service.js.map +1 -1
  173. package/dist/services/orm/printer-tag.service.js.map +1 -1
  174. package/dist/services/orm/printer.service.js.map +1 -1
  175. package/dist/services/orm/refresh-token.service.js.map +1 -1
  176. package/dist/services/orm/role.service.js.map +1 -1
  177. package/dist/services/orm/settings.service.js.map +1 -1
  178. package/dist/services/orm/user-role.service.js.map +1 -1
  179. package/dist/services/orm/user.service.js.map +1 -1
  180. package/dist/services/print-file-downloader.service.js.map +1 -1
  181. package/dist/services/print-queue.service.js.map +1 -1
  182. package/dist/services/printer-api.factory.js.map +1 -1
  183. package/dist/services/printer-api.interface.js.map +1 -1
  184. package/dist/services/prusa-link/prusa-link-http-polling.adapter.js.map +1 -1
  185. package/dist/services/prusa-link/prusa-link.api.js.map +1 -1
  186. package/dist/services/prusa-link/utils/digest-auth.util.js +19 -12
  187. package/dist/services/prusa-link/utils/digest-auth.util.js.map +1 -1
  188. package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js +45 -11
  189. package/dist/services/prusa-link/utils/prusa-link-http-client.builder.js.map +1 -1
  190. package/dist/services/socket.factory.js.map +1 -1
  191. package/dist/services/task-manager.service.js.map +1 -1
  192. package/dist/services/typeorm/typeorm.service.js.map +1 -1
  193. package/dist/services/validators/printer-service.validation.js.map +1 -1
  194. package/dist/shared/default-http-client.builder.js.map +1 -1
  195. package/dist/shared/load-controllers.js.map +1 -1
  196. package/dist/shared/runtime-settings.migration.js.map +1 -1
  197. package/dist/shared/websocket-rpc-extended.adapter.js.map +1 -1
  198. package/dist/shared/websocket.adapter.js.map +1 -1
  199. package/dist/state/file-upload-tracker.cache.js.map +1 -1
  200. package/dist/state/floor.store.js.map +1 -1
  201. package/dist/state/printer-events.cache.js.map +1 -1
  202. package/dist/state/printer-socket.store.js.map +1 -1
  203. package/dist/state/printer-thumbnail.cache.js.map +1 -1
  204. package/dist/state/printer.cache.js.map +1 -1
  205. package/dist/state/settings.store.js.map +1 -1
  206. package/dist/state/socket-io.gateway.js.map +1 -1
  207. package/dist/state/test-printer-socket.store.js.map +1 -1
  208. package/dist/tasks/boot.task.js.map +1 -1
  209. package/dist/tasks/client-bundle.task.js.map +1 -1
  210. package/dist/tasks/print-job-analysis.task.js.map +1 -1
  211. package/dist/tasks/printer-websocket-restore.task.js.map +1 -1
  212. package/dist/tasks/printer-websocket.task.js.map +1 -1
  213. package/dist/tasks/socketio.task.js.map +1 -1
  214. package/dist/tasks/software-update.task.js.map +1 -1
  215. package/dist/tasks.js.map +1 -1
  216. package/dist/utils/array.util.js.map +1 -1
  217. package/dist/utils/bgcode/bgcode-thumbnail.parser.js.map +1 -1
  218. package/dist/utils/bgcode/bgcode.utils.js.map +1 -1
  219. package/dist/utils/bgcode/heatshrink-decoder.js.map +1 -1
  220. package/dist/utils/bgcode/png-encoder.js.map +1 -1
  221. package/dist/utils/bgcode/qoi-decoder.js.map +1 -1
  222. package/dist/utils/cache/key-diff.cache.js.map +1 -1
  223. package/dist/utils/correlation-token.util.js.map +1 -1
  224. package/dist/utils/crypto.utils.js.map +1 -1
  225. package/dist/utils/env.utils.js.map +1 -1
  226. package/dist/utils/error.utils.js.map +1 -1
  227. package/dist/utils/fs.utils.js.map +1 -1
  228. package/dist/utils/gcode.utils.js.map +1 -1
  229. package/dist/utils/image-dimensions.js.map +1 -1
  230. package/dist/utils/job-stats.util.js.map +1 -1
  231. package/dist/utils/normalize-url.js.map +1 -1
  232. package/dist/utils/parsers/3mf.parser.js.map +1 -1
  233. package/dist/utils/parsers/bgcode.parser.js.map +1 -1
  234. package/dist/utils/parsers/gcode.parser.js.map +1 -1
  235. package/dist/utils/pretty-print.utils.js.map +1 -1
  236. package/dist/utils/semver.utils.js.map +1 -1
  237. package/dist/utils/swagger/decorators.js.map +1 -1
  238. package/dist/utils/swagger/generator.js.map +1 -1
  239. package/dist/utils/swagger/swagger.js.map +1 -1
  240. package/dist/utils/thumbnail.util.js.map +1 -1
  241. package/dist/utils/time.utils.js.map +1 -1
  242. package/dist/utils/url.utils.js.map +1 -1
  243. package/package.json +17 -14
  244. package/packages/consoles/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"heatshrink-decoder.js","names":[],"sources":["../../../src/utils/bgcode/heatshrink-decoder.ts"],"sourcesContent":["/**\n * Heatshrink LZSS decompressor implementation\n * Based on the Heatshrink compression library by Scott Vokes\n * https://github.com/atomicobject/heatshrink\n */\n\nenum HSState {\n TAG_BIT,\n YIELD_LITERAL,\n BACKREF_INDEX_MSB,\n BACKREF_INDEX_LSB,\n BACKREF_COUNT_MSB,\n BACKREF_COUNT_LSB,\n YIELD_BACKREF,\n}\n\nexport class HeatshrinkDecoder {\n private readonly windowBits: number;\n private readonly lookaheadBits: number;\n private readonly windowSize: number;\n\n private readonly window: Buffer;\n private head: number = 0;\n private state: HSState = HSState.TAG_BIT;\n\n private outputIndex: number = 0;\n private outputCount: number = 0;\n\n private inputBuffer: Buffer;\n private inputIndex: number = 0;\n private bitAccumulator: number = 0;\n private bitsAvailable: number = 0;\n\n constructor(windowBits: number, lookaheadBits: number) {\n this.windowBits = windowBits;\n this.lookaheadBits = lookaheadBits;\n this.windowSize = 1 << windowBits; // 2^windowBits\n\n this.window = Buffer.alloc(this.windowSize);\n this.inputBuffer = Buffer.alloc(0);\n }\n\n decompress(input: Buffer): Buffer {\n this.inputBuffer = input;\n this.inputIndex = 0;\n this.bitAccumulator = 0;\n this.bitsAvailable = 0;\n this.head = 0;\n this.state = HSState.TAG_BIT;\n\n const output: number[] = [];\n\n while (this.inputIndex < this.inputBuffer.length || this.bitsAvailable > 0) {\n const result = this.step();\n if (result === null) {\n break; // No more data\n }\n if (result >= 0) {\n output.push(result);\n }\n }\n\n return Buffer.from(output);\n }\n\n private step(): number | null {\n switch (this.state) {\n case HSState.TAG_BIT:\n return this.handleTagBit();\n case HSState.YIELD_LITERAL:\n return this.handleYieldLiteral();\n case HSState.BACKREF_INDEX_MSB:\n return this.handleBackrefIndexMSB();\n case HSState.BACKREF_INDEX_LSB:\n return this.handleBackrefIndexLSB();\n case HSState.BACKREF_COUNT_MSB:\n return this.handleBackrefCountMSB();\n case HSState.BACKREF_COUNT_LSB:\n return this.handleBackrefCountLSB();\n case HSState.YIELD_BACKREF:\n return this.handleYieldBackref();\n default:\n return null;\n }\n }\n\n private readBits(count: number): number | null {\n // Fill accumulator if needed\n while (this.bitsAvailable < count && this.inputIndex < this.inputBuffer.length) {\n this.bitAccumulator = (this.bitAccumulator << 8) | this.inputBuffer[this.inputIndex++];\n this.bitsAvailable += 8;\n }\n\n if (this.bitsAvailable < count) {\n return null; // Not enough bits\n }\n\n // Extract the requested bits\n this.bitsAvailable -= count;\n const result = (this.bitAccumulator >> this.bitsAvailable) & ((1 << count) - 1);\n this.bitAccumulator &= (1 << this.bitsAvailable) - 1;\n\n return result;\n }\n\n private handleTagBit(): number | null {\n const bit = this.readBits(1);\n if (bit === null) return null;\n\n if (bit === 1) {\n // Literal byte\n this.state = HSState.YIELD_LITERAL;\n } else {\n // Backref\n if (this.windowBits > 8) {\n this.state = HSState.BACKREF_INDEX_MSB;\n } else {\n this.outputIndex = 0;\n this.state = HSState.BACKREF_INDEX_LSB;\n }\n }\n return -1; // No output yet\n }\n\n private handleYieldLiteral(): number | null {\n const byte = this.readBits(8);\n if (byte === null) return null;\n\n this.pushByte(byte);\n this.state = HSState.TAG_BIT;\n return byte;\n }\n\n private handleBackrefIndexMSB(): number | null {\n const msb = this.readBits(this.windowBits - 8);\n if (msb === null) return null;\n\n this.outputIndex = msb << 8;\n this.state = HSState.BACKREF_INDEX_LSB;\n return -1;\n }\n\n private handleBackrefIndexLSB(): number | null {\n const bits = this.windowBits > 8 ? 8 : this.windowBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputIndex |= lsb;\n this.outputCount = 0;\n\n if (this.lookaheadBits > 8) {\n this.state = HSState.BACKREF_COUNT_MSB;\n } else {\n this.state = HSState.BACKREF_COUNT_LSB;\n }\n return -1;\n }\n\n private handleBackrefCountMSB(): number | null {\n const msb = this.readBits(this.lookaheadBits - 8);\n if (msb === null) return null;\n\n this.outputCount = msb << 8;\n this.state = HSState.BACKREF_COUNT_LSB;\n return -1;\n }\n\n private handleBackrefCountLSB(): number | null {\n const bits = this.lookaheadBits > 8 ? 8 : this.lookaheadBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputCount |= lsb;\n this.outputCount += 1; // Count is stored as length-1\n this.outputIndex += 1; // Index is stored as (actual_index - 1)\n\n this.state = HSState.YIELD_BACKREF;\n return -1;\n }\n\n private handleYieldBackref(): number | null {\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n return -1;\n }\n\n // Calculate the position in the window\n const pos = (this.head - this.outputIndex + this.windowSize) % this.windowSize;\n const byte = this.window[pos];\n\n this.pushByte(byte);\n this.outputCount--;\n\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n }\n\n return byte;\n }\n\n private pushByte(byte: number): void {\n this.window[this.head] = byte;\n this.head = (this.head + 1) % this.windowSize;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"heatshrink-decoder.js","names":[],"sources":["../../../src/utils/bgcode/heatshrink-decoder.ts"],"sourcesContent":["/**\n * Heatshrink LZSS decompressor implementation\n * Based on the Heatshrink compression library by Scott Vokes\n * https://github.com/atomicobject/heatshrink\n */\n\nenum HSState {\n TAG_BIT,\n YIELD_LITERAL,\n BACKREF_INDEX_MSB,\n BACKREF_INDEX_LSB,\n BACKREF_COUNT_MSB,\n BACKREF_COUNT_LSB,\n YIELD_BACKREF,\n}\n\nexport class HeatshrinkDecoder {\n private readonly windowBits: number;\n private readonly lookaheadBits: number;\n private readonly windowSize: number;\n\n private readonly window: Buffer;\n private head: number = 0;\n private state: HSState = HSState.TAG_BIT;\n\n private outputIndex: number = 0;\n private outputCount: number = 0;\n\n private inputBuffer: Buffer;\n private inputIndex: number = 0;\n private bitAccumulator: number = 0;\n private bitsAvailable: number = 0;\n\n constructor(windowBits: number, lookaheadBits: number) {\n this.windowBits = windowBits;\n this.lookaheadBits = lookaheadBits;\n this.windowSize = 1 << windowBits; // 2^windowBits\n\n this.window = Buffer.alloc(this.windowSize);\n this.inputBuffer = Buffer.alloc(0);\n }\n\n decompress(input: Buffer): Buffer {\n this.inputBuffer = input;\n this.inputIndex = 0;\n this.bitAccumulator = 0;\n this.bitsAvailable = 0;\n this.head = 0;\n this.state = HSState.TAG_BIT;\n\n const output: number[] = [];\n\n while (this.inputIndex < this.inputBuffer.length || this.bitsAvailable > 0) {\n const result = this.step();\n if (result === null) {\n break; // No more data\n }\n if (result >= 0) {\n output.push(result);\n }\n }\n\n return Buffer.from(output);\n }\n\n private step(): number | null {\n switch (this.state) {\n case HSState.TAG_BIT:\n return this.handleTagBit();\n case HSState.YIELD_LITERAL:\n return this.handleYieldLiteral();\n case HSState.BACKREF_INDEX_MSB:\n return this.handleBackrefIndexMSB();\n case HSState.BACKREF_INDEX_LSB:\n return this.handleBackrefIndexLSB();\n case HSState.BACKREF_COUNT_MSB:\n return this.handleBackrefCountMSB();\n case HSState.BACKREF_COUNT_LSB:\n return this.handleBackrefCountLSB();\n case HSState.YIELD_BACKREF:\n return this.handleYieldBackref();\n default:\n return null;\n }\n }\n\n private readBits(count: number): number | null {\n // Fill accumulator if needed\n while (this.bitsAvailable < count && this.inputIndex < this.inputBuffer.length) {\n this.bitAccumulator = (this.bitAccumulator << 8) | this.inputBuffer[this.inputIndex++];\n this.bitsAvailable += 8;\n }\n\n if (this.bitsAvailable < count) {\n return null; // Not enough bits\n }\n\n // Extract the requested bits\n this.bitsAvailable -= count;\n const result = (this.bitAccumulator >> this.bitsAvailable) & ((1 << count) - 1);\n this.bitAccumulator &= (1 << this.bitsAvailable) - 1;\n\n return result;\n }\n\n private handleTagBit(): number | null {\n const bit = this.readBits(1);\n if (bit === null) return null;\n\n if (bit === 1) {\n // Literal byte\n this.state = HSState.YIELD_LITERAL;\n } else {\n // Backref\n if (this.windowBits > 8) {\n this.state = HSState.BACKREF_INDEX_MSB;\n } else {\n this.outputIndex = 0;\n this.state = HSState.BACKREF_INDEX_LSB;\n }\n }\n return -1; // No output yet\n }\n\n private handleYieldLiteral(): number | null {\n const byte = this.readBits(8);\n if (byte === null) return null;\n\n this.pushByte(byte);\n this.state = HSState.TAG_BIT;\n return byte;\n }\n\n private handleBackrefIndexMSB(): number | null {\n const msb = this.readBits(this.windowBits - 8);\n if (msb === null) return null;\n\n this.outputIndex = msb << 8;\n this.state = HSState.BACKREF_INDEX_LSB;\n return -1;\n }\n\n private handleBackrefIndexLSB(): number | null {\n const bits = this.windowBits > 8 ? 8 : this.windowBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputIndex |= lsb;\n this.outputCount = 0;\n\n if (this.lookaheadBits > 8) {\n this.state = HSState.BACKREF_COUNT_MSB;\n } else {\n this.state = HSState.BACKREF_COUNT_LSB;\n }\n return -1;\n }\n\n private handleBackrefCountMSB(): number | null {\n const msb = this.readBits(this.lookaheadBits - 8);\n if (msb === null) return null;\n\n this.outputCount = msb << 8;\n this.state = HSState.BACKREF_COUNT_LSB;\n return -1;\n }\n\n private handleBackrefCountLSB(): number | null {\n const bits = this.lookaheadBits > 8 ? 8 : this.lookaheadBits;\n const lsb = this.readBits(bits);\n if (lsb === null) return null;\n\n this.outputCount |= lsb;\n this.outputCount += 1; // Count is stored as length-1\n this.outputIndex += 1; // Index is stored as (actual_index - 1)\n\n this.state = HSState.YIELD_BACKREF;\n return -1;\n }\n\n private handleYieldBackref(): number | null {\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n return -1;\n }\n\n // Calculate the position in the window\n const pos = (this.head - this.outputIndex + this.windowSize) % this.windowSize;\n const byte = this.window[pos];\n\n this.pushByte(byte);\n this.outputCount--;\n\n if (this.outputCount === 0) {\n this.state = HSState.TAG_BIT;\n }\n\n return byte;\n }\n\n private pushByte(byte: number): void {\n this.window[this.head] = byte;\n this.head = (this.head + 1) % this.windowSize;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"png-encoder.js","names":[],"sources":["../../../src/utils/bgcode/png-encoder.ts"],"sourcesContent":["import { deflateSync } from \"node:zlib\";\n\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\nfunction crc32(buffer: Buffer): number {\n let crc = 0xffffffff;\n for (const element of buffer) {\n crc ^= element;\n for (let j = 0; j < 8; j++) {\n crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));\n }\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction writeChunk(type: string, data: Buffer): Buffer {\n const typeBuffer = Buffer.from(type, \"ascii\");\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length, 0);\n\n const crcBuffer = Buffer.concat([typeBuffer, data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(crcBuffer), 0);\n\n return Buffer.concat([length, typeBuffer, data, crc]);\n}\n\nexport function encodePNG(width: number, height: number, rgba: Buffer): Buffer {\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr.writeUInt8(8, 8); // bit depth\n ihdr.writeUInt8(6, 9); // color type (6 = RGBA)\n ihdr.writeUInt8(0, 10); // compression method\n ihdr.writeUInt8(0, 11); // filter method\n ihdr.writeUInt8(0, 12); // interlace method\n\n // Prepare image data with filter bytes (0 = no filter for each scanline)\n const bytesPerPixel = 4;\n const scanlineLength = width * bytesPerPixel;\n const filteredData = Buffer.alloc(height * (scanlineLength + 1));\n\n for (let y = 0; y < height; y++) {\n const scanlineOffset = y * (scanlineLength + 1);\n filteredData[scanlineOffset] = 0; // Filter type: None\n rgba.copy(filteredData, scanlineOffset + 1, y * scanlineLength, (y + 1) * scanlineLength);\n }\n\n const compressed = deflateSync(filteredData);\n const chunks = [\n PNG_SIGNATURE,\n writeChunk(\"IHDR\", ihdr),\n writeChunk(\"IDAT\", compressed),\n writeChunk(\"IEND\", Buffer.alloc(0)),\n ];\n\n return Buffer.concat(chunks);\n}\n"],"mappings":";;AAEA,MAAM,gBAAgB,OAAO,KAAK;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAEnF,SAAS,MAAM,QAAwB;CACrC,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,QAAQ;AAC5B,SAAO;AACP,OAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,OAAO,QAAQ,IAAM,aAAa,EAAE,MAAM;;AAG9C,SAAQ,MAAM,gBAAgB;;AAGhC,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ;CAC7C,MAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,QAAO,cAAc,KAAK,QAAQ,EAAE;CAEpC,MAAM,YAAY,OAAO,OAAO,CAAC,YAAY,KAAK,CAAC;CACnD,MAAM,MAAM,OAAO,MAAM,EAAE;AAC3B,KAAI,cAAc,MAAM,UAAU,EAAE,EAAE;AAEtC,QAAO,OAAO,OAAO;EAAC;EAAQ;EAAY;EAAM;EAAI,CAAC;;AAGvD,SAAgB,UAAU,OAAe,QAAgB,MAAsB;CAC7E,MAAM,OAAO,OAAO,MAAM,GAAG;AAC7B,MAAK,cAAc,OAAO,EAAE;AAC5B,MAAK,cAAc,QAAQ,EAAE;AAC7B,MAAK,WAAW,GAAG,EAAE;AACrB,MAAK,WAAW,GAAG,EAAE;AACrB,MAAK,WAAW,GAAG,GAAG;AACtB,MAAK,WAAW,GAAG,GAAG;AACtB,MAAK,WAAW,GAAG,GAAG;CAItB,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,eAAe,OAAO,MAAM,UAAU,iBAAiB,GAAG;AAEhE,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,iBAAiB,KAAK,iBAAiB;AAC7C,eAAa,kBAAkB;AAC/B,OAAK,KAAK,cAAc,iBAAiB,GAAG,IAAI,iBAAiB,IAAI,KAAK,eAAe;;CAG3F,MAAM,aAAa,YAAY,aAAa;CAC5C,MAAM,SAAS;EACb;EACA,WAAW,QAAQ,KAAK;EACxB,WAAW,QAAQ,WAAW;EAC9B,WAAW,QAAQ,OAAO,MAAM,EAAE,CAAC;EACpC;AAED,QAAO,OAAO,OAAO,OAAO"}
1
+ {"version":3,"file":"png-encoder.js","names":[],"sources":["../../../src/utils/bgcode/png-encoder.ts"],"sourcesContent":["import { deflateSync } from \"node:zlib\";\n\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);\n\nfunction crc32(buffer: Buffer): number {\n let crc = 0xffffffff;\n for (const element of buffer) {\n crc ^= element;\n for (let j = 0; j < 8; j++) {\n crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));\n }\n }\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction writeChunk(type: string, data: Buffer): Buffer {\n const typeBuffer = Buffer.from(type, \"ascii\");\n const length = Buffer.alloc(4);\n length.writeUInt32BE(data.length, 0);\n\n const crcBuffer = Buffer.concat([typeBuffer, data]);\n const crc = Buffer.alloc(4);\n crc.writeUInt32BE(crc32(crcBuffer), 0);\n\n return Buffer.concat([length, typeBuffer, data, crc]);\n}\n\nexport function encodePNG(width: number, height: number, rgba: Buffer): Buffer {\n const ihdr = Buffer.alloc(13);\n ihdr.writeUInt32BE(width, 0);\n ihdr.writeUInt32BE(height, 4);\n ihdr.writeUInt8(8, 8); // bit depth\n ihdr.writeUInt8(6, 9); // color type (6 = RGBA)\n ihdr.writeUInt8(0, 10); // compression method\n ihdr.writeUInt8(0, 11); // filter method\n ihdr.writeUInt8(0, 12); // interlace method\n\n // Prepare image data with filter bytes (0 = no filter for each scanline)\n const bytesPerPixel = 4;\n const scanlineLength = width * bytesPerPixel;\n const filteredData = Buffer.alloc(height * (scanlineLength + 1));\n\n for (let y = 0; y < height; y++) {\n const scanlineOffset = y * (scanlineLength + 1);\n filteredData[scanlineOffset] = 0; // Filter type: None\n rgba.copy(filteredData, scanlineOffset + 1, y * scanlineLength, (y + 1) * scanlineLength);\n }\n\n const compressed = deflateSync(filteredData);\n const chunks = [\n PNG_SIGNATURE,\n writeChunk(\"IHDR\", ihdr),\n writeChunk(\"IDAT\", compressed),\n writeChunk(\"IEND\", Buffer.alloc(0)),\n ];\n\n return Buffer.concat(chunks);\n}\n"],"mappings":";;AAEA,MAAM,gBAAgB,OAAO,KAAK;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAK,CAAC;AAEnF,SAAS,MAAM,QAAwB;CACrC,IAAI,MAAM;CACV,KAAK,MAAM,WAAW,QAAQ;EAC5B,OAAO;EACP,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,MAAO,QAAQ,IAAM,aAAa,EAAE,MAAM;;CAG9C,QAAQ,MAAM,gBAAgB;;AAGhC,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ;CAC7C,MAAM,SAAS,OAAO,MAAM,EAAE;CAC9B,OAAO,cAAc,KAAK,QAAQ,EAAE;CAEpC,MAAM,YAAY,OAAO,OAAO,CAAC,YAAY,KAAK,CAAC;CACnD,MAAM,MAAM,OAAO,MAAM,EAAE;CAC3B,IAAI,cAAc,MAAM,UAAU,EAAE,EAAE;CAEtC,OAAO,OAAO,OAAO;EAAC;EAAQ;EAAY;EAAM;EAAI,CAAC;;AAGvD,SAAgB,UAAU,OAAe,QAAgB,MAAsB;CAC7E,MAAM,OAAO,OAAO,MAAM,GAAG;CAC7B,KAAK,cAAc,OAAO,EAAE;CAC5B,KAAK,cAAc,QAAQ,EAAE;CAC7B,KAAK,WAAW,GAAG,EAAE;CACrB,KAAK,WAAW,GAAG,EAAE;CACrB,KAAK,WAAW,GAAG,GAAG;CACtB,KAAK,WAAW,GAAG,GAAG;CACtB,KAAK,WAAW,GAAG,GAAG;CAItB,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,eAAe,OAAO,MAAM,UAAU,iBAAiB,GAAG;CAEhE,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,iBAAiB,KAAK,iBAAiB;EAC7C,aAAa,kBAAkB;EAC/B,KAAK,KAAK,cAAc,iBAAiB,GAAG,IAAI,iBAAiB,IAAI,KAAK,eAAe;;CAG3F,MAAM,aAAa,YAAY,aAAa;CAC5C,MAAM,SAAS;EACb;EACA,WAAW,QAAQ,KAAK;EACxB,WAAW,QAAQ,WAAW;EAC9B,WAAW,QAAQ,OAAO,MAAM,EAAE,CAAC;EACpC;CAED,OAAO,OAAO,OAAO,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"qoi-decoder.js","names":[],"sources":["../../../src/utils/bgcode/qoi-decoder.ts"],"sourcesContent":["interface QOIDecoded {\n width: number;\n height: number;\n channels: 3 | 4;\n colorspace: 0 | 1;\n data: Buffer;\n}\n\nconst QOI_OP_INDEX = 0x00; // 00xxxxxx\nconst QOI_OP_DIFF = 0x40; // 01xxxxxx\nconst QOI_OP_LUMA = 0x80; // 10xxxxxx\nconst QOI_OP_RUN = 0xc0; // 11xxxxxx\nconst QOI_OP_RGB = 0xfe; // 11111110\nconst QOI_OP_RGBA = 0xff; // 11111111\n\nconst QOI_MAGIC = 0x716f6966; // \"qoif\"\n\nfunction hashColor(r: number, g: number, b: number, a: number): number {\n return (r * 3 + g * 5 + b * 7 + a * 11) % 64;\n}\n\nexport function decodeQOI(buffer: Buffer): QOIDecoded {\n let pos = 0;\n\n // Read header (14 bytes)\n if (buffer.length < 14) {\n throw new Error(\"Invalid QOI file: too short\");\n }\n\n const magic = buffer.readUInt32BE(pos);\n pos += 4;\n if (magic !== QOI_MAGIC) {\n throw new Error(`Invalid QOI magic: expected ${QOI_MAGIC.toString(16)}, got ${magic.toString(16)}`);\n }\n\n const width = buffer.readUInt32BE(pos);\n pos += 4;\n const height = buffer.readUInt32BE(pos);\n pos += 4;\n const channels = buffer.readUInt8(pos) as 3 | 4;\n pos += 1;\n const colorspace = buffer.readUInt8(pos) as 0 | 1;\n pos += 1;\n\n if (channels !== 3 && channels !== 4) {\n throw new Error(`Invalid QOI channels: ${channels}`);\n }\n\n // Allocate output buffer\n const pixelCount = width * height;\n const outputSize = pixelCount * 4; // Always output RGBA\n const output = Buffer.alloc(outputSize);\n\n // Initialize state\n const colorArray: Array<[number, number, number, number]> = new Array(64);\n for (let i = 0; i < 64; i++) {\n colorArray[i] = [0, 0, 0, 0];\n }\n\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 255;\n let outPos = 0;\n\n // Decode chunks\n while (outPos < outputSize) {\n // Check for end marker (7 bytes of 0x00 followed by 0x01)\n if (pos + 8 <= buffer.length) {\n let isEnd = true;\n for (let i = 0; i < 7; i++) {\n if (buffer[pos + i] !== 0x00) {\n isEnd = false;\n break;\n }\n }\n if (isEnd && buffer[pos + 7] === 0x01) {\n break;\n }\n }\n\n if (pos >= buffer.length) {\n throw new Error(\"Unexpected end of QOI data\");\n }\n\n const byte1 = buffer[pos++];\n\n if (byte1 === QOI_OP_RGB) {\n // QOI_OP_RGB: full RGB\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n } else if (byte1 === QOI_OP_RGBA) {\n // QOI_OP_RGBA: full RGBA\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n a = buffer[pos++];\n } else {\n const tag = byte1 & 0xc0;\n\n if (tag === QOI_OP_INDEX) {\n // QOI_OP_INDEX: 00xxxxxx\n const index = byte1 & 0x3f;\n [r, g, b, a] = colorArray[index];\n } else if (tag === QOI_OP_DIFF) {\n // QOI_OP_DIFF: 01xxxxxx\n const dr = ((byte1 >> 4) & 0x03) - 2;\n const dg = ((byte1 >> 2) & 0x03) - 2;\n const db = (byte1 & 0x03) - 2;\n r = (r + dr) & 0xff;\n g = (g + dg) & 0xff;\n b = (b + db) & 0xff;\n } else if (tag === QOI_OP_LUMA) {\n // QOI_OP_LUMA: 10xxxxxx\n const byte2 = buffer[pos++];\n const dg = (byte1 & 0x3f) - 32;\n const dr_dg = ((byte2 >> 4) & 0x0f) - 8;\n const db_dg = (byte2 & 0x0f) - 8;\n g = (g + dg) & 0xff;\n r = (r + dg + dr_dg) & 0xff;\n b = (b + dg + db_dg) & 0xff;\n } else if (tag === QOI_OP_RUN) {\n // QOI_OP_RUN: 11xxxxxx\n const run = (byte1 & 0x3f) + 1;\n for (let i = 0; i < run; i++) {\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n }\n // Update color array for the last pixel in run\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n continue;\n }\n }\n\n // Write pixel\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n\n // Update color array\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n }\n\n return {\n width,\n height,\n channels,\n colorspace,\n data: output,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"qoi-decoder.js","names":[],"sources":["../../../src/utils/bgcode/qoi-decoder.ts"],"sourcesContent":["interface QOIDecoded {\n width: number;\n height: number;\n channels: 3 | 4;\n colorspace: 0 | 1;\n data: Buffer;\n}\n\nconst QOI_OP_INDEX = 0x00; // 00xxxxxx\nconst QOI_OP_DIFF = 0x40; // 01xxxxxx\nconst QOI_OP_LUMA = 0x80; // 10xxxxxx\nconst QOI_OP_RUN = 0xc0; // 11xxxxxx\nconst QOI_OP_RGB = 0xfe; // 11111110\nconst QOI_OP_RGBA = 0xff; // 11111111\n\nconst QOI_MAGIC = 0x716f6966; // \"qoif\"\n\nfunction hashColor(r: number, g: number, b: number, a: number): number {\n return (r * 3 + g * 5 + b * 7 + a * 11) % 64;\n}\n\nexport function decodeQOI(buffer: Buffer): QOIDecoded {\n let pos = 0;\n\n // Read header (14 bytes)\n if (buffer.length < 14) {\n throw new Error(\"Invalid QOI file: too short\");\n }\n\n const magic = buffer.readUInt32BE(pos);\n pos += 4;\n if (magic !== QOI_MAGIC) {\n throw new Error(`Invalid QOI magic: expected ${QOI_MAGIC.toString(16)}, got ${magic.toString(16)}`);\n }\n\n const width = buffer.readUInt32BE(pos);\n pos += 4;\n const height = buffer.readUInt32BE(pos);\n pos += 4;\n const channels = buffer.readUInt8(pos) as 3 | 4;\n pos += 1;\n const colorspace = buffer.readUInt8(pos) as 0 | 1;\n pos += 1;\n\n if (channels !== 3 && channels !== 4) {\n throw new Error(`Invalid QOI channels: ${channels}`);\n }\n\n // Allocate output buffer\n const pixelCount = width * height;\n const outputSize = pixelCount * 4; // Always output RGBA\n const output = Buffer.alloc(outputSize);\n\n // Initialize state\n const colorArray: Array<[number, number, number, number]> = new Array(64);\n for (let i = 0; i < 64; i++) {\n colorArray[i] = [0, 0, 0, 0];\n }\n\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 255;\n let outPos = 0;\n\n // Decode chunks\n while (outPos < outputSize) {\n // Check for end marker (7 bytes of 0x00 followed by 0x01)\n if (pos + 8 <= buffer.length) {\n let isEnd = true;\n for (let i = 0; i < 7; i++) {\n if (buffer[pos + i] !== 0x00) {\n isEnd = false;\n break;\n }\n }\n if (isEnd && buffer[pos + 7] === 0x01) {\n break;\n }\n }\n\n if (pos >= buffer.length) {\n throw new Error(\"Unexpected end of QOI data\");\n }\n\n const byte1 = buffer[pos++];\n\n if (byte1 === QOI_OP_RGB) {\n // QOI_OP_RGB: full RGB\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n } else if (byte1 === QOI_OP_RGBA) {\n // QOI_OP_RGBA: full RGBA\n r = buffer[pos++];\n g = buffer[pos++];\n b = buffer[pos++];\n a = buffer[pos++];\n } else {\n const tag = byte1 & 0xc0;\n\n if (tag === QOI_OP_INDEX) {\n // QOI_OP_INDEX: 00xxxxxx\n const index = byte1 & 0x3f;\n [r, g, b, a] = colorArray[index];\n } else if (tag === QOI_OP_DIFF) {\n // QOI_OP_DIFF: 01xxxxxx\n const dr = ((byte1 >> 4) & 0x03) - 2;\n const dg = ((byte1 >> 2) & 0x03) - 2;\n const db = (byte1 & 0x03) - 2;\n r = (r + dr) & 0xff;\n g = (g + dg) & 0xff;\n b = (b + db) & 0xff;\n } else if (tag === QOI_OP_LUMA) {\n // QOI_OP_LUMA: 10xxxxxx\n const byte2 = buffer[pos++];\n const dg = (byte1 & 0x3f) - 32;\n const dr_dg = ((byte2 >> 4) & 0x0f) - 8;\n const db_dg = (byte2 & 0x0f) - 8;\n g = (g + dg) & 0xff;\n r = (r + dg + dr_dg) & 0xff;\n b = (b + dg + db_dg) & 0xff;\n } else if (tag === QOI_OP_RUN) {\n // QOI_OP_RUN: 11xxxxxx\n const run = (byte1 & 0x3f) + 1;\n for (let i = 0; i < run; i++) {\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n }\n // Update color array for the last pixel in run\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n continue;\n }\n }\n\n // Write pixel\n output[outPos++] = r;\n output[outPos++] = g;\n output[outPos++] = b;\n output[outPos++] = a;\n\n // Update color array\n const hash = hashColor(r, g, b, a);\n colorArray[hash] = [r, g, b, a];\n }\n\n return {\n width,\n height,\n channels,\n colorspace,\n data: output,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"key-diff.cache.js","names":[],"sources":["../../../src/utils/cache/key-diff.cache.ts"],"sourcesContent":["export class KeyDiffCache<T> {\n deletedKeys: number[] = [];\n updatedKeys: number[] = [];\n\n protected keyValueStore = new Map<number, T>();\n\n public async getAllValues(): Promise<T[]> {\n return Array.from(this.keyValueStore.values());\n }\n\n public async getAllKeyValues(): Promise<Record<number, T>> {\n return Object.fromEntries(this.keyValueStore);\n }\n\n public async getValue(key: number): Promise<T | undefined> {\n return this.keyValueStore.get(key);\n }\n\n protected async setKeyValuesBatch(keyValues: Array<{ key: number; value: T }>, markUpdated: boolean = true) {\n for (const { key, value } of keyValues) {\n await this.setKeyValue(key, value, false);\n }\n if (markUpdated) {\n const updatedKeys = keyValues.map(({ key }) => key);\n this.batchMarkUpdated(updatedKeys);\n }\n }\n\n protected async deleteKeysBatch(keys: number[], markDeleted: boolean = true) {\n for (const key of keys) {\n await this.deleteKeyValue(key, false);\n }\n if (markDeleted) {\n this.batchMarkDeleted(keys);\n }\n }\n\n protected async setKeyValue(key: number, value: T, markUpdated: boolean = true) {\n this.keyValueStore.set(key, value);\n if (markUpdated) {\n this.markUpdated(key);\n }\n }\n\n protected async deleteKeyValue(key: number, markDeleted: boolean = true) {\n this.keyValueStore.delete(key);\n if (markDeleted) {\n this.markDeleted(key);\n }\n }\n\n protected batchMarkDeleted(keys: number[]) {\n for (const key of keys) {\n this.markDeleted(key);\n }\n }\n\n protected markUpdated(key: number) {\n const deletedIndex = this.deletedKeys.indexOf(key);\n if (deletedIndex !== -1) {\n this.deletedKeys.splice(deletedIndex, 1);\n }\n if (!this.updatedKeys.includes(key)) {\n this.updatedKeys.push(key);\n }\n }\n\n protected markDeleted(key: number) {\n const updatedIndex = this.updatedKeys.indexOf(key);\n if (updatedIndex !== -1) {\n this.updatedKeys.splice(updatedIndex, 1);\n }\n if (!this.deletedKeys.includes(key)) {\n this.deletedKeys.push(key);\n }\n }\n\n protected resetDiffs() {\n this.deletedKeys = [];\n this.updatedKeys = [];\n }\n\n private batchMarkUpdated(keys: number[]) {\n for (const key of keys) {\n this.markUpdated(key);\n }\n }\n}\n"],"mappings":";AAAA,IAAa,eAAb,MAA6B;CAC3B,cAAwB,EAAE;CAC1B,cAAwB,EAAE;CAE1B,gCAA0B,IAAI,KAAgB;CAE9C,MAAa,eAA6B;AACxC,SAAO,MAAM,KAAK,KAAK,cAAc,QAAQ,CAAC;;CAGhD,MAAa,kBAA8C;AACzD,SAAO,OAAO,YAAY,KAAK,cAAc;;CAG/C,MAAa,SAAS,KAAqC;AACzD,SAAO,KAAK,cAAc,IAAI,IAAI;;CAGpC,MAAgB,kBAAkB,WAA6C,cAAuB,MAAM;AAC1G,OAAK,MAAM,EAAE,KAAK,WAAW,UAC3B,OAAM,KAAK,YAAY,KAAK,OAAO,MAAM;AAE3C,MAAI,aAAa;GACf,MAAM,cAAc,UAAU,KAAK,EAAE,UAAU,IAAI;AACnD,QAAK,iBAAiB,YAAY;;;CAItC,MAAgB,gBAAgB,MAAgB,cAAuB,MAAM;AAC3E,OAAK,MAAM,OAAO,KAChB,OAAM,KAAK,eAAe,KAAK,MAAM;AAEvC,MAAI,YACF,MAAK,iBAAiB,KAAK;;CAI/B,MAAgB,YAAY,KAAa,OAAU,cAAuB,MAAM;AAC9E,OAAK,cAAc,IAAI,KAAK,MAAM;AAClC,MAAI,YACF,MAAK,YAAY,IAAI;;CAIzB,MAAgB,eAAe,KAAa,cAAuB,MAAM;AACvE,OAAK,cAAc,OAAO,IAAI;AAC9B,MAAI,YACF,MAAK,YAAY,IAAI;;CAIzB,iBAA2B,MAAgB;AACzC,OAAK,MAAM,OAAO,KAChB,MAAK,YAAY,IAAI;;CAIzB,YAAsB,KAAa;EACjC,MAAM,eAAe,KAAK,YAAY,QAAQ,IAAI;AAClD,MAAI,iBAAiB,GACnB,MAAK,YAAY,OAAO,cAAc,EAAE;AAE1C,MAAI,CAAC,KAAK,YAAY,SAAS,IAAI,CACjC,MAAK,YAAY,KAAK,IAAI;;CAI9B,YAAsB,KAAa;EACjC,MAAM,eAAe,KAAK,YAAY,QAAQ,IAAI;AAClD,MAAI,iBAAiB,GACnB,MAAK,YAAY,OAAO,cAAc,EAAE;AAE1C,MAAI,CAAC,KAAK,YAAY,SAAS,IAAI,CACjC,MAAK,YAAY,KAAK,IAAI;;CAI9B,aAAuB;AACrB,OAAK,cAAc,EAAE;AACrB,OAAK,cAAc,EAAE;;CAGvB,iBAAyB,MAAgB;AACvC,OAAK,MAAM,OAAO,KAChB,MAAK,YAAY,IAAI"}
1
+ {"version":3,"file":"key-diff.cache.js","names":[],"sources":["../../../src/utils/cache/key-diff.cache.ts"],"sourcesContent":["export class KeyDiffCache<T> {\n deletedKeys: number[] = [];\n updatedKeys: number[] = [];\n\n protected keyValueStore = new Map<number, T>();\n\n public async getAllValues(): Promise<T[]> {\n return Array.from(this.keyValueStore.values());\n }\n\n public async getAllKeyValues(): Promise<Record<number, T>> {\n return Object.fromEntries(this.keyValueStore);\n }\n\n public async getValue(key: number): Promise<T | undefined> {\n return this.keyValueStore.get(key);\n }\n\n protected async setKeyValuesBatch(keyValues: Array<{ key: number; value: T }>, markUpdated: boolean = true) {\n for (const { key, value } of keyValues) {\n await this.setKeyValue(key, value, false);\n }\n if (markUpdated) {\n const updatedKeys = keyValues.map(({ key }) => key);\n this.batchMarkUpdated(updatedKeys);\n }\n }\n\n protected async deleteKeysBatch(keys: number[], markDeleted: boolean = true) {\n for (const key of keys) {\n await this.deleteKeyValue(key, false);\n }\n if (markDeleted) {\n this.batchMarkDeleted(keys);\n }\n }\n\n protected async setKeyValue(key: number, value: T, markUpdated: boolean = true) {\n this.keyValueStore.set(key, value);\n if (markUpdated) {\n this.markUpdated(key);\n }\n }\n\n protected async deleteKeyValue(key: number, markDeleted: boolean = true) {\n this.keyValueStore.delete(key);\n if (markDeleted) {\n this.markDeleted(key);\n }\n }\n\n protected batchMarkDeleted(keys: number[]) {\n for (const key of keys) {\n this.markDeleted(key);\n }\n }\n\n protected markUpdated(key: number) {\n const deletedIndex = this.deletedKeys.indexOf(key);\n if (deletedIndex !== -1) {\n this.deletedKeys.splice(deletedIndex, 1);\n }\n if (!this.updatedKeys.includes(key)) {\n this.updatedKeys.push(key);\n }\n }\n\n protected markDeleted(key: number) {\n const updatedIndex = this.updatedKeys.indexOf(key);\n if (updatedIndex !== -1) {\n this.updatedKeys.splice(updatedIndex, 1);\n }\n if (!this.deletedKeys.includes(key)) {\n this.deletedKeys.push(key);\n }\n }\n\n protected resetDiffs() {\n this.deletedKeys = [];\n this.updatedKeys = [];\n }\n\n private batchMarkUpdated(keys: number[]) {\n for (const key of keys) {\n this.markUpdated(key);\n }\n }\n}\n"],"mappings":";AAAA,IAAa,eAAb,MAA6B;CAC3B,cAAwB,EAAE;CAC1B,cAAwB,EAAE;CAE1B,gCAA0B,IAAI,KAAgB;CAE9C,MAAa,eAA6B;EACxC,OAAO,MAAM,KAAK,KAAK,cAAc,QAAQ,CAAC;;CAGhD,MAAa,kBAA8C;EACzD,OAAO,OAAO,YAAY,KAAK,cAAc;;CAG/C,MAAa,SAAS,KAAqC;EACzD,OAAO,KAAK,cAAc,IAAI,IAAI;;CAGpC,MAAgB,kBAAkB,WAA6C,cAAuB,MAAM;EAC1G,KAAK,MAAM,EAAE,KAAK,WAAW,WAC3B,MAAM,KAAK,YAAY,KAAK,OAAO,MAAM;EAE3C,IAAI,aAAa;GACf,MAAM,cAAc,UAAU,KAAK,EAAE,UAAU,IAAI;GACnD,KAAK,iBAAiB,YAAY;;;CAItC,MAAgB,gBAAgB,MAAgB,cAAuB,MAAM;EAC3E,KAAK,MAAM,OAAO,MAChB,MAAM,KAAK,eAAe,KAAK,MAAM;EAEvC,IAAI,aACF,KAAK,iBAAiB,KAAK;;CAI/B,MAAgB,YAAY,KAAa,OAAU,cAAuB,MAAM;EAC9E,KAAK,cAAc,IAAI,KAAK,MAAM;EAClC,IAAI,aACF,KAAK,YAAY,IAAI;;CAIzB,MAAgB,eAAe,KAAa,cAAuB,MAAM;EACvE,KAAK,cAAc,OAAO,IAAI;EAC9B,IAAI,aACF,KAAK,YAAY,IAAI;;CAIzB,iBAA2B,MAAgB;EACzC,KAAK,MAAM,OAAO,MAChB,KAAK,YAAY,IAAI;;CAIzB,YAAsB,KAAa;EACjC,MAAM,eAAe,KAAK,YAAY,QAAQ,IAAI;EAClD,IAAI,iBAAiB,IACnB,KAAK,YAAY,OAAO,cAAc,EAAE;EAE1C,IAAI,CAAC,KAAK,YAAY,SAAS,IAAI,EACjC,KAAK,YAAY,KAAK,IAAI;;CAI9B,YAAsB,KAAa;EACjC,MAAM,eAAe,KAAK,YAAY,QAAQ,IAAI;EAClD,IAAI,iBAAiB,IACnB,KAAK,YAAY,OAAO,cAAc,EAAE;EAE1C,IAAI,CAAC,KAAK,YAAY,SAAS,IAAI,EACjC,KAAK,YAAY,KAAK,IAAI;;CAI9B,aAAuB;EACrB,KAAK,cAAc,EAAE;EACrB,KAAK,cAAc,EAAE;;CAGvB,iBAAyB,MAAgB;EACvC,KAAK,MAAM,OAAO,MAChB,KAAK,YAAY,IAAI"}
@@ -1 +1 @@
1
- {"version":3,"file":"correlation-token.util.js","names":[],"sources":["../../src/utils/correlation-token.util.ts"],"sourcesContent":["export function generateCorrelationToken() {\n return Math.random().toString(36).slice(2);\n}\n"],"mappings":";AAAA,SAAgB,2BAA2B;AACzC,QAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE"}
1
+ {"version":3,"file":"correlation-token.util.js","names":[],"sources":["../../src/utils/correlation-token.util.ts"],"sourcesContent":["export function generateCorrelationToken() {\n return Math.random().toString(36).slice(2);\n}\n"],"mappings":";AAAA,SAAgB,2BAA2B;CACzC,OAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.utils.js","names":[],"sources":["../../src/utils/crypto.utils.ts"],"sourcesContent":["import { compareSync, genSaltSync, hashSync } from \"bcryptjs\";\n\nexport function hashPassword(password: string) {\n const salt = genSaltSync(10);\n return hashSync(password, salt);\n}\n\nexport function comparePasswordHash(password: string, passwordHash: string): boolean {\n if (!password?.length) return false;\n return compareSync(password, passwordHash);\n}\n"],"mappings":";;AAEA,SAAgB,aAAa,UAAkB;AAE7C,QAAO,SAAS,UADH,YAAY,GACK,CAAC;;AAGjC,SAAgB,oBAAoB,UAAkB,cAA+B;AACnF,KAAI,CAAC,UAAU,OAAQ,QAAO;AAC9B,QAAO,YAAY,UAAU,aAAa"}
1
+ {"version":3,"file":"crypto.utils.js","names":[],"sources":["../../src/utils/crypto.utils.ts"],"sourcesContent":["import { compareSync, genSaltSync, hashSync } from \"bcryptjs\";\n\nexport function hashPassword(password: string) {\n const salt = genSaltSync(10);\n return hashSync(password, salt);\n}\n\nexport function comparePasswordHash(password: string, passwordHash: string): boolean {\n if (!password?.length) return false;\n return compareSync(password, passwordHash);\n}\n"],"mappings":";;AAEA,SAAgB,aAAa,UAAkB;CAE7C,OAAO,SAAS,UADH,YAAY,GACK,CAAC;;AAGjC,SAAgB,oBAAoB,UAAkB,cAA+B;CACnF,IAAI,CAAC,UAAU,QAAQ,OAAO;CAC9B,OAAO,YAAY,UAAU,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"env.utils.js","names":[],"sources":["../../src/utils/env.utils.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport dotenv from \"dotenv\";\nimport { join } from \"node:path\";\nimport { superRootPath } from \"@/utils/fs.utils\";\n\nif (process.env.NODE_ENV !== \"test\") {\n dotenv.config({\n path: process.env.ENV_FILE || join(superRootPath(), \".env\"),\n });\n}\n\nexport function getEnvOrDefault<T>(key: string, defaultValue: T) {\n if (!Object.keys(process.env).includes(key) || !process.env[key]?.length) {\n return defaultValue;\n }\n return process.env[key] as T;\n}\n\nexport function isDevelopmentEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultDevelopmentEnv;\n}\n\nexport function isTestEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultTestEnv;\n}\n\nexport function isProductionEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultProductionEnv;\n}\n\nexport function isNode() {\n return \"NODE\" in process.env;\n}\n"],"mappings":";;;;;AAKA,IAAI,QAAQ,IAAI,aAAa,OAC3B,QAAO,OAAO,EACZ,MAAM,QAAQ,IAAI,YAAY,KAAK,eAAe,EAAE,OAAO,EAC5D,CAAC;AAGJ,SAAgB,gBAAmB,KAAa,cAAiB;AAC/D,KAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,MAAM,OAChE,QAAO;AAET,QAAO,QAAQ,IAAI;;AAGrB,SAAgB,2BAA2B;AACzC,QAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,oBAAoB;AAClC,QAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,0BAA0B;AACxC,QAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,SAAS;AACvB,QAAO,UAAU,QAAQ"}
1
+ {"version":3,"file":"env.utils.js","names":[],"sources":["../../src/utils/env.utils.ts"],"sourcesContent":["import { AppConstants } from \"@/server.constants\";\nimport dotenv from \"dotenv\";\nimport { join } from \"node:path\";\nimport { superRootPath } from \"@/utils/fs.utils\";\n\nif (process.env.NODE_ENV !== \"test\") {\n dotenv.config({\n path: process.env.ENV_FILE || join(superRootPath(), \".env\"),\n });\n}\n\nexport function getEnvOrDefault<T>(key: string, defaultValue: T) {\n if (!Object.keys(process.env).includes(key) || !process.env[key]?.length) {\n return defaultValue;\n }\n return process.env[key] as T;\n}\n\nexport function isDevelopmentEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultDevelopmentEnv;\n}\n\nexport function isTestEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultTestEnv;\n}\n\nexport function isProductionEnvironment() {\n return process.env.NODE_ENV === AppConstants.defaultProductionEnv;\n}\n\nexport function isNode() {\n return \"NODE\" in process.env;\n}\n"],"mappings":";;;;;AAKA,IAAI,QAAQ,IAAI,aAAa,QAC3B,OAAO,OAAO,EACZ,MAAM,QAAQ,IAAI,YAAY,KAAK,eAAe,EAAE,OAAO,EAC5D,CAAC;AAGJ,SAAgB,gBAAmB,KAAa,cAAiB;CAC/D,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,MAAM,QAChE,OAAO;CAET,OAAO,QAAQ,IAAI;;AAGrB,SAAgB,2BAA2B;CACzC,OAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,oBAAoB;CAClC,OAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,0BAA0B;CACxC,OAAO,QAAQ,IAAI,aAAa,aAAa;;AAG/C,SAAgB,SAAS;CACvB,OAAO,UAAU,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"error.utils.js","names":[],"sources":["../../src/utils/error.utils.ts"],"sourcesContent":["export function errorSummary(e: any) {\n return e.message ? `${e.message}\\n ${e.stack}` : `'${e}'`;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,GAAQ;AACnC,QAAO,EAAE,UAAU,GAAG,EAAE,QAAQ,KAAK,EAAE,UAAU,IAAI,EAAE"}
1
+ {"version":3,"file":"error.utils.js","names":[],"sources":["../../src/utils/error.utils.ts"],"sourcesContent":["export function errorSummary(e: any) {\n return e.message ? `${e.message}\\n ${e.stack}` : `'${e}'`;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,GAAQ;CACnC,OAAO,EAAE,UAAU,GAAG,EAAE,QAAQ,KAAK,EAAE,UAAU,IAAI,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"fs.utils.js","names":[],"sources":["../../src/utils/fs.utils.ts"],"sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { AppConstants } from \"@/server.constants\";\nimport { getEnvOrDefault } from \"@/utils/env.utils\";\n\n/**\n * Get __dirname equivalent in ESM\n * @param importMetaUrl - Pass import.meta.url from the calling module\n */\nexport function getDirname(importMetaUrl: string): string {\n return dirname(fileURLToPath(importMetaUrl));\n}\n\nexport function getDatabaseFilePath() {\n const dbFile = getEnvOrDefault(AppConstants.DATABASE_FILE, AppConstants.defaultDatabaseFile);\n if (dbFile === \":memory:\") {\n return dbFile;\n }\n\n const dbFolder = getDatabaseFolder();\n return join(dbFolder, dbFile);\n}\n\nexport function getDatabaseFolder() {\n return getEnvOrDefault(AppConstants.DATABASE_PATH, join(superRootPath(), AppConstants.defaultDatabasePath));\n}\n\nexport function getMediaPath() {\n return getEnvOrDefault(AppConstants.MEDIA_PATH, join(superRootPath(), AppConstants.defaultBaseMediaPath));\n}\n\nexport function packageJsonPath() {\n return join(superRootPath(), \"./package.json\");\n}\n\nexport function ensureDirExists(dir: string) {\n if (existsSync(dir)) {\n return;\n }\n\n mkdirSync(dir, { recursive: true });\n}\n\n/**\n * Root where code is hosted, avoid using excessively\n */\nexport function superRootPath() {\n return join(getDirname(import.meta.url), \"../..\");\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,WAAW,eAA+B;AACxD,QAAO,QAAQ,cAAc,cAAc,CAAC;;AAG9C,SAAgB,sBAAsB;CACpC,MAAM,SAAS,gBAAgB,aAAa,eAAe,aAAa,oBAAoB;AAC5F,KAAI,WAAW,WACb,QAAO;AAIT,QAAO,KADU,mBACG,EAAE,OAAO;;AAG/B,SAAgB,oBAAoB;AAClC,QAAO,gBAAgB,aAAa,eAAe,KAAK,eAAe,EAAE,aAAa,oBAAoB,CAAC;;AAG7G,SAAgB,eAAe;AAC7B,QAAO,gBAAgB,aAAa,YAAY,KAAK,eAAe,EAAE,aAAa,qBAAqB,CAAC;;AAG3G,SAAgB,kBAAkB;AAChC,QAAO,KAAK,eAAe,EAAE,iBAAiB;;AAGhD,SAAgB,gBAAgB,KAAa;AAC3C,KAAI,WAAW,IAAI,CACjB;AAGF,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;;;;;AAMrC,SAAgB,gBAAgB;AAC9B,QAAO,KAAK,WAAW,OAAO,KAAK,IAAI,EAAE,QAAQ"}
1
+ {"version":3,"file":"fs.utils.js","names":[],"sources":["../../src/utils/fs.utils.ts"],"sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { AppConstants } from \"@/server.constants\";\nimport { getEnvOrDefault } from \"@/utils/env.utils\";\n\n/**\n * Get __dirname equivalent in ESM\n * @param importMetaUrl - Pass import.meta.url from the calling module\n */\nexport function getDirname(importMetaUrl: string): string {\n return dirname(fileURLToPath(importMetaUrl));\n}\n\nexport function getDatabaseFilePath() {\n const dbFile = getEnvOrDefault(AppConstants.DATABASE_FILE, AppConstants.defaultDatabaseFile);\n if (dbFile === \":memory:\") {\n return dbFile;\n }\n\n const dbFolder = getDatabaseFolder();\n return join(dbFolder, dbFile);\n}\n\nexport function getDatabaseFolder() {\n return getEnvOrDefault(AppConstants.DATABASE_PATH, join(superRootPath(), AppConstants.defaultDatabasePath));\n}\n\nexport function getMediaPath() {\n return getEnvOrDefault(AppConstants.MEDIA_PATH, join(superRootPath(), AppConstants.defaultBaseMediaPath));\n}\n\nexport function packageJsonPath() {\n return join(superRootPath(), \"./package.json\");\n}\n\nexport function ensureDirExists(dir: string) {\n if (existsSync(dir)) {\n return;\n }\n\n mkdirSync(dir, { recursive: true });\n}\n\n/**\n * Root where code is hosted, avoid using excessively\n */\nexport function superRootPath() {\n return join(getDirname(import.meta.url), \"../..\");\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,WAAW,eAA+B;CACxD,OAAO,QAAQ,cAAc,cAAc,CAAC;;AAG9C,SAAgB,sBAAsB;CACpC,MAAM,SAAS,gBAAgB,aAAa,eAAe,aAAa,oBAAoB;CAC5F,IAAI,WAAW,YACb,OAAO;CAIT,OAAO,KADU,mBACG,EAAE,OAAO;;AAG/B,SAAgB,oBAAoB;CAClC,OAAO,gBAAgB,aAAa,eAAe,KAAK,eAAe,EAAE,aAAa,oBAAoB,CAAC;;AAG7G,SAAgB,eAAe;CAC7B,OAAO,gBAAgB,aAAa,YAAY,KAAK,eAAe,EAAE,aAAa,qBAAqB,CAAC;;AAG3G,SAAgB,kBAAkB;CAChC,OAAO,KAAK,eAAe,EAAE,iBAAiB;;AAGhD,SAAgB,gBAAgB,KAAa;CAC3C,IAAI,WAAW,IAAI,EACjB;CAGF,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;;;;;AAMrC,SAAgB,gBAAgB;CAC9B,OAAO,KAAK,WAAW,OAAO,KAAK,IAAI,EAAE,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"gcode.utils.js","names":[],"sources":["../../src/utils/gcode.utils.ts"],"sourcesContent":["import { open } from \"node:fs/promises\";\n\nexport const gcodeScanningChunkSize = 64 * 1024; // 64 KB\n/**\n * Read local GCode metadata or thumbnails\n * @param filePath\n * @param numberOfLines\n */\nexport async function readLastLinesLocal(filePath: string, numberOfLines: number) {\n const file = await open(filePath, \"r\");\n\n try {\n const buffer = Buffer.alloc(gcodeScanningChunkSize);\n const fileSize = await file.stat();\n let lines: string[] = [];\n let position = fileSize.size;\n let iterationsLeft = 100;\n\n while (lines.length <= numberOfLines && position > 0) {\n iterationsLeft--;\n if (iterationsLeft <= 0) {\n throw new Error(\"Too many iterations reached, 'readLastLines' aborted\");\n }\n\n const bytesToRead = Math.min(gcodeScanningChunkSize, position);\n position -= bytesToRead;\n\n await file.read(buffer, 0, bytesToRead, position);\n const chunk = buffer.toString(\"utf-8\", 0, bytesToRead);\n\n // Prepend the chunk's lines\n lines = chunk.split(\"\\n\").concat(lines);\n\n await file.close();\n }\n\n return lines.slice(-numberOfLines);\n } catch (e) {\n await file.close();\n throw e;\n }\n}\n"],"mappings":";;AAEA,MAAa,yBAAyB,KAAK;;;;;;AAM3C,eAAsB,mBAAmB,UAAkB,eAAuB;CAChF,MAAM,OAAO,MAAM,KAAK,UAAU,IAAI;AAEtC,KAAI;EACF,MAAM,SAAS,OAAO,MAAM,uBAAuB;EACnD,MAAM,WAAW,MAAM,KAAK,MAAM;EAClC,IAAI,QAAkB,EAAE;EACxB,IAAI,WAAW,SAAS;EACxB,IAAI,iBAAiB;AAErB,SAAO,MAAM,UAAU,iBAAiB,WAAW,GAAG;AACpD;AACA,OAAI,kBAAkB,EACpB,OAAM,IAAI,MAAM,uDAAuD;GAGzE,MAAM,cAAc,KAAK,IAAI,wBAAwB,SAAS;AAC9D,eAAY;AAEZ,SAAM,KAAK,KAAK,QAAQ,GAAG,aAAa,SAAS;AAIjD,WAHc,OAAO,SAAS,SAAS,GAAG,YAG7B,CAAC,MAAM,KAAK,CAAC,OAAO,MAAM;AAEvC,SAAM,KAAK,OAAO;;AAGpB,SAAO,MAAM,MAAM,CAAC,cAAc;UAC3B,GAAG;AACV,QAAM,KAAK,OAAO;AAClB,QAAM"}
1
+ {"version":3,"file":"gcode.utils.js","names":[],"sources":["../../src/utils/gcode.utils.ts"],"sourcesContent":["import { open } from \"node:fs/promises\";\n\nexport const gcodeScanningChunkSize = 64 * 1024; // 64 KB\n/**\n * Read local GCode metadata or thumbnails\n * @param filePath\n * @param numberOfLines\n */\nexport async function readLastLinesLocal(filePath: string, numberOfLines: number) {\n const file = await open(filePath, \"r\");\n\n try {\n const buffer = Buffer.alloc(gcodeScanningChunkSize);\n const fileSize = await file.stat();\n let lines: string[] = [];\n let position = fileSize.size;\n let iterationsLeft = 100;\n\n while (lines.length <= numberOfLines && position > 0) {\n iterationsLeft--;\n if (iterationsLeft <= 0) {\n throw new Error(\"Too many iterations reached, 'readLastLines' aborted\");\n }\n\n const bytesToRead = Math.min(gcodeScanningChunkSize, position);\n position -= bytesToRead;\n\n await file.read(buffer, 0, bytesToRead, position);\n const chunk = buffer.toString(\"utf-8\", 0, bytesToRead);\n\n // Prepend the chunk's lines\n lines = chunk.split(\"\\n\").concat(lines);\n\n await file.close();\n }\n\n return lines.slice(-numberOfLines);\n } catch (e) {\n await file.close();\n throw e;\n }\n}\n"],"mappings":";;AAEA,MAAa,yBAAyB,KAAK;;;;;;AAM3C,eAAsB,mBAAmB,UAAkB,eAAuB;CAChF,MAAM,OAAO,MAAM,KAAK,UAAU,IAAI;CAEtC,IAAI;EACF,MAAM,SAAS,OAAO,MAAM,uBAAuB;EACnD,MAAM,WAAW,MAAM,KAAK,MAAM;EAClC,IAAI,QAAkB,EAAE;EACxB,IAAI,WAAW,SAAS;EACxB,IAAI,iBAAiB;EAErB,OAAO,MAAM,UAAU,iBAAiB,WAAW,GAAG;GACpD;GACA,IAAI,kBAAkB,GACpB,MAAM,IAAI,MAAM,uDAAuD;GAGzE,MAAM,cAAc,KAAK,IAAI,wBAAwB,SAAS;GAC9D,YAAY;GAEZ,MAAM,KAAK,KAAK,QAAQ,GAAG,aAAa,SAAS;GAIjD,QAHc,OAAO,SAAS,SAAS,GAAG,YAG7B,CAAC,MAAM,KAAK,CAAC,OAAO,MAAM;GAEvC,MAAM,KAAK,OAAO;;EAGpB,OAAO,MAAM,MAAM,CAAC,cAAc;UAC3B,GAAG;EACV,MAAM,KAAK,OAAO;EAClB,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"image-dimensions.js","names":[],"sources":["../../src/utils/image-dimensions.ts"],"sourcesContent":["export function getImageDimensions(imageData: Buffer, format: string): { width: number; height: number } {\n if (format === \"PNG\") {\n return getPngDimensions(imageData);\n } else if (format === \"JPG\" || format === \"JPEG\") {\n return getJpgDimensions(imageData);\n }\n return { width: 0, height: 0 };\n}\n\nfunction getPngDimensions(data: Buffer): { width: number; height: number } {\n if (data.length < 24) {\n return { width: 0, height: 0 };\n }\n\n const width = data.readUInt32BE(16);\n const height = data.readUInt32BE(20);\n\n return { width, height };\n}\n\nfunction getJpgDimensions(data: Buffer): { width: number; height: number } {\n let offset = 2;\n\n while (offset < data.length) {\n if (data[offset] !== 0xff) break;\n\n const marker = data[offset + 1];\n offset += 2;\n\n if (marker === 0xc0 || marker === 0xc2) {\n if (offset + 5 < data.length) {\n const height = data.readUInt16BE(offset + 1);\n const width = data.readUInt16BE(offset + 3);\n return { width, height };\n }\n break;\n }\n\n const segmentLength = data.readUInt16BE(offset);\n offset += segmentLength;\n }\n\n return { width: 0, height: 0 };\n}\n"],"mappings":";AAAA,SAAgB,mBAAmB,WAAmB,QAAmD;AACvG,KAAI,WAAW,MACb,QAAO,iBAAiB,UAAU;UACzB,WAAW,SAAS,WAAW,OACxC,QAAO,iBAAiB,UAAU;AAEpC,QAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;;AAGhC,SAAS,iBAAiB,MAAiD;AACzE,KAAI,KAAK,SAAS,GAChB,QAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;AAMhC,QAAO;EAAE,OAHK,KAAK,aAAa,GAGlB;EAAE,QAFD,KAAK,aAAa,GAEX;EAAE;;AAG1B,SAAS,iBAAiB,MAAiD;CACzE,IAAI,SAAS;AAEb,QAAO,SAAS,KAAK,QAAQ;AAC3B,MAAI,KAAK,YAAY,IAAM;EAE3B,MAAM,SAAS,KAAK,SAAS;AAC7B,YAAU;AAEV,MAAI,WAAW,OAAQ,WAAW,KAAM;AACtC,OAAI,SAAS,IAAI,KAAK,QAAQ;IAC5B,MAAM,SAAS,KAAK,aAAa,SAAS,EAAE;AAE5C,WAAO;KAAE,OADK,KAAK,aAAa,SAAS,EAC3B;KAAE;KAAQ;;AAE1B;;EAGF,MAAM,gBAAgB,KAAK,aAAa,OAAO;AAC/C,YAAU;;AAGZ,QAAO;EAAE,OAAO;EAAG,QAAQ;EAAG"}
1
+ {"version":3,"file":"image-dimensions.js","names":[],"sources":["../../src/utils/image-dimensions.ts"],"sourcesContent":["export function getImageDimensions(imageData: Buffer, format: string): { width: number; height: number } {\n if (format === \"PNG\") {\n return getPngDimensions(imageData);\n } else if (format === \"JPG\" || format === \"JPEG\") {\n return getJpgDimensions(imageData);\n }\n return { width: 0, height: 0 };\n}\n\nfunction getPngDimensions(data: Buffer): { width: number; height: number } {\n if (data.length < 24) {\n return { width: 0, height: 0 };\n }\n\n const width = data.readUInt32BE(16);\n const height = data.readUInt32BE(20);\n\n return { width, height };\n}\n\nfunction getJpgDimensions(data: Buffer): { width: number; height: number } {\n let offset = 2;\n\n while (offset < data.length) {\n if (data[offset] !== 0xff) break;\n\n const marker = data[offset + 1];\n offset += 2;\n\n if (marker === 0xc0 || marker === 0xc2) {\n if (offset + 5 < data.length) {\n const height = data.readUInt16BE(offset + 1);\n const width = data.readUInt16BE(offset + 3);\n return { width, height };\n }\n break;\n }\n\n const segmentLength = data.readUInt16BE(offset);\n offset += segmentLength;\n }\n\n return { width: 0, height: 0 };\n}\n"],"mappings":";AAAA,SAAgB,mBAAmB,WAAmB,QAAmD;CACvG,IAAI,WAAW,OACb,OAAO,iBAAiB,UAAU;MAC7B,IAAI,WAAW,SAAS,WAAW,QACxC,OAAO,iBAAiB,UAAU;CAEpC,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;;AAGhC,SAAS,iBAAiB,MAAiD;CACzE,IAAI,KAAK,SAAS,IAChB,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAG;CAMhC,OAAO;EAAE,OAHK,KAAK,aAAa,GAGlB;EAAE,QAFD,KAAK,aAAa,GAEX;EAAE;;AAG1B,SAAS,iBAAiB,MAAiD;CACzE,IAAI,SAAS;CAEb,OAAO,SAAS,KAAK,QAAQ;EAC3B,IAAI,KAAK,YAAY,KAAM;EAE3B,MAAM,SAAS,KAAK,SAAS;EAC7B,UAAU;EAEV,IAAI,WAAW,OAAQ,WAAW,KAAM;GACtC,IAAI,SAAS,IAAI,KAAK,QAAQ;IAC5B,MAAM,SAAS,KAAK,aAAa,SAAS,EAAE;IAE5C,OAAO;KAAE,OADK,KAAK,aAAa,SAAS,EAC3B;KAAE;KAAQ;;GAE1B;;EAGF,MAAM,gBAAgB,KAAK,aAAa,OAAO;EAC/C,UAAU;;CAGZ,OAAO;EAAE,OAAO;EAAG,QAAQ;EAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"job-stats.util.js","names":[],"sources":["../../src/utils/job-stats.util.ts"],"sourcesContent":["import { PrintJob, PrintStatistics } from \"@/entities/print-job.entity\";\n\nexport function calculateJobDuration(startedAt: Date | null, endedAt: Date = new Date()): number | null {\n if (!startedAt) {\n return null;\n }\n return (endedAt.getTime() - startedAt.getTime()) / 1000;\n}\n\nexport interface StatisticsUpdateOptions {\n progress?: number;\n failureReason?: string;\n failureTime?: Date;\n}\n\nexport function initializeOrUpdateStatistics(\n job: PrintJob,\n endedAt: Date = new Date(),\n options: StatisticsUpdateOptions = {},\n): PrintStatistics {\n const actualPrintTimeSeconds = calculateJobDuration(job.startedAt, endedAt);\n\n if (!job.statistics) {\n return {\n startedAt: job.startedAt,\n endedAt,\n actualPrintTimeSeconds,\n progress: options.progress ?? job.progress ?? null,\n failureReason: options.failureReason,\n failureTime: options.failureTime,\n };\n } else {\n job.statistics.endedAt = endedAt;\n job.statistics.actualPrintTimeSeconds = actualPrintTimeSeconds;\n\n if (options.progress !== undefined) {\n job.statistics.progress = options.progress;\n }\n\n if (options.failureReason !== undefined) {\n job.statistics.failureReason = options.failureReason;\n job.statistics.failureTime = options.failureTime ?? endedAt;\n }\n\n return job.statistics;\n }\n}\n\nexport function updateStatisticsForCompletion(job: PrintJob, endedAt: Date = new Date()): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt, { progress: 100 });\n job.endedAt = endedAt;\n job.progress = 100;\n}\n\nexport function updateStatisticsForFailure(job: PrintJob, reason: string, endedAt: Date = new Date()): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt, {\n failureReason: reason,\n failureTime: endedAt,\n });\n job.endedAt = endedAt;\n job.statusReason = reason;\n}\n\nexport function updateStatisticsForCancellation(\n job: PrintJob,\n reason: string = \"Print cancelled by user\",\n endedAt: Date = new Date(),\n): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt);\n job.endedAt = endedAt;\n job.statusReason = reason;\n}\n"],"mappings":";AAEA,SAAgB,qBAAqB,WAAwB,0BAAgB,IAAI,MAAM,EAAiB;AACtG,KAAI,CAAC,UACH,QAAO;AAET,SAAQ,QAAQ,SAAS,GAAG,UAAU,SAAS,IAAI;;AASrD,SAAgB,6BACd,KACA,0BAAgB,IAAI,MAAM,EAC1B,UAAmC,EAAE,EACpB;CACjB,MAAM,yBAAyB,qBAAqB,IAAI,WAAW,QAAQ;AAE3E,KAAI,CAAC,IAAI,WACP,QAAO;EACL,WAAW,IAAI;EACf;EACA;EACA,UAAU,QAAQ,YAAY,IAAI,YAAY;EAC9C,eAAe,QAAQ;EACvB,aAAa,QAAQ;EACtB;MACI;AACL,MAAI,WAAW,UAAU;AACzB,MAAI,WAAW,yBAAyB;AAExC,MAAI,QAAQ,aAAa,KAAA,EACvB,KAAI,WAAW,WAAW,QAAQ;AAGpC,MAAI,QAAQ,kBAAkB,KAAA,GAAW;AACvC,OAAI,WAAW,gBAAgB,QAAQ;AACvC,OAAI,WAAW,cAAc,QAAQ,eAAe;;AAGtD,SAAO,IAAI;;;AAIf,SAAgB,8BAA8B,KAAe,0BAAgB,IAAI,MAAM,EAAQ;AAC7F,KAAI,aAAa,6BAA6B,KAAK,SAAS,EAAE,UAAU,KAAK,CAAC;AAC9E,KAAI,UAAU;AACd,KAAI,WAAW;;AAGjB,SAAgB,2BAA2B,KAAe,QAAgB,0BAAgB,IAAI,MAAM,EAAQ;AAC1G,KAAI,aAAa,6BAA6B,KAAK,SAAS;EAC1D,eAAe;EACf,aAAa;EACd,CAAC;AACF,KAAI,UAAU;AACd,KAAI,eAAe;;AAGrB,SAAgB,gCACd,KACA,SAAiB,2BACjB,0BAAgB,IAAI,MAAM,EACpB;AACN,KAAI,aAAa,6BAA6B,KAAK,QAAQ;AAC3D,KAAI,UAAU;AACd,KAAI,eAAe"}
1
+ {"version":3,"file":"job-stats.util.js","names":[],"sources":["../../src/utils/job-stats.util.ts"],"sourcesContent":["import { PrintJob, PrintStatistics } from \"@/entities/print-job.entity\";\n\nexport function calculateJobDuration(startedAt: Date | null, endedAt: Date = new Date()): number | null {\n if (!startedAt) {\n return null;\n }\n return (endedAt.getTime() - startedAt.getTime()) / 1000;\n}\n\nexport interface StatisticsUpdateOptions {\n progress?: number;\n failureReason?: string;\n failureTime?: Date;\n}\n\nexport function initializeOrUpdateStatistics(\n job: PrintJob,\n endedAt: Date = new Date(),\n options: StatisticsUpdateOptions = {},\n): PrintStatistics {\n const actualPrintTimeSeconds = calculateJobDuration(job.startedAt, endedAt);\n\n if (!job.statistics) {\n return {\n startedAt: job.startedAt,\n endedAt,\n actualPrintTimeSeconds,\n progress: options.progress ?? job.progress ?? null,\n failureReason: options.failureReason,\n failureTime: options.failureTime,\n };\n } else {\n job.statistics.endedAt = endedAt;\n job.statistics.actualPrintTimeSeconds = actualPrintTimeSeconds;\n\n if (options.progress !== undefined) {\n job.statistics.progress = options.progress;\n }\n\n if (options.failureReason !== undefined) {\n job.statistics.failureReason = options.failureReason;\n job.statistics.failureTime = options.failureTime ?? endedAt;\n }\n\n return job.statistics;\n }\n}\n\nexport function updateStatisticsForCompletion(job: PrintJob, endedAt: Date = new Date()): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt, { progress: 100 });\n job.endedAt = endedAt;\n job.progress = 100;\n}\n\nexport function updateStatisticsForFailure(job: PrintJob, reason: string, endedAt: Date = new Date()): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt, {\n failureReason: reason,\n failureTime: endedAt,\n });\n job.endedAt = endedAt;\n job.statusReason = reason;\n}\n\nexport function updateStatisticsForCancellation(\n job: PrintJob,\n reason: string = \"Print cancelled by user\",\n endedAt: Date = new Date(),\n): void {\n job.statistics = initializeOrUpdateStatistics(job, endedAt);\n job.endedAt = endedAt;\n job.statusReason = reason;\n}\n"],"mappings":";AAEA,SAAgB,qBAAqB,WAAwB,0BAAgB,IAAI,MAAM,EAAiB;CACtG,IAAI,CAAC,WACH,OAAO;CAET,QAAQ,QAAQ,SAAS,GAAG,UAAU,SAAS,IAAI;;AASrD,SAAgB,6BACd,KACA,0BAAgB,IAAI,MAAM,EAC1B,UAAmC,EAAE,EACpB;CACjB,MAAM,yBAAyB,qBAAqB,IAAI,WAAW,QAAQ;CAE3E,IAAI,CAAC,IAAI,YACP,OAAO;EACL,WAAW,IAAI;EACf;EACA;EACA,UAAU,QAAQ,YAAY,IAAI,YAAY;EAC9C,eAAe,QAAQ;EACvB,aAAa,QAAQ;EACtB;MACI;EACL,IAAI,WAAW,UAAU;EACzB,IAAI,WAAW,yBAAyB;EAExC,IAAI,QAAQ,aAAa,KAAA,GACvB,IAAI,WAAW,WAAW,QAAQ;EAGpC,IAAI,QAAQ,kBAAkB,KAAA,GAAW;GACvC,IAAI,WAAW,gBAAgB,QAAQ;GACvC,IAAI,WAAW,cAAc,QAAQ,eAAe;;EAGtD,OAAO,IAAI;;;AAIf,SAAgB,8BAA8B,KAAe,0BAAgB,IAAI,MAAM,EAAQ;CAC7F,IAAI,aAAa,6BAA6B,KAAK,SAAS,EAAE,UAAU,KAAK,CAAC;CAC9E,IAAI,UAAU;CACd,IAAI,WAAW;;AAGjB,SAAgB,2BAA2B,KAAe,QAAgB,0BAAgB,IAAI,MAAM,EAAQ;CAC1G,IAAI,aAAa,6BAA6B,KAAK,SAAS;EAC1D,eAAe;EACf,aAAa;EACd,CAAC;CACF,IAAI,UAAU;CACd,IAAI,eAAe;;AAGrB,SAAgB,gCACd,KACA,SAAiB,2BACjB,0BAAgB,IAAI,MAAM,EACpB;CACN,IAAI,aAAa,6BAA6B,KAAK,QAAQ;CAC3D,IAAI,UAAU;CACd,IAAI,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-url.js","names":[],"sources":["../../src/utils/normalize-url.ts"],"sourcesContent":["// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs\nimport { defaultHttpProtocol } from \"@/utils/url.utils\";\n\nconst DATA_URL_DEFAULT_MIME_TYPE = \"text/plain\";\nconst DATA_URL_DEFAULT_CHARSET = \"us-ascii\";\n\nconst testParameter = (name: string, filters: (RegExp | any)[]) =>\n filters.some((filter) => (filter instanceof RegExp ? filter.test(name) : filter === name));\n\nconst supportedProtocols = new Set([\"https:\", \"http:\", \"file:\"]);\n\nconst hasCustomProtocol = (urlString: string) => {\n try {\n const { protocol } = new URL(urlString);\n\n return protocol.endsWith(\":\") && !protocol.includes(\".\") && !supportedProtocols.has(protocol);\n } catch {\n return false;\n }\n};\n\nconst normalizeDataURL = (urlString: string, { stripHash }: { stripHash?: boolean }) => {\n const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);\n\n if (!match?.groups) {\n throw new Error(`Invalid URL: ${urlString}`);\n }\n\n let { type, data, hash } = match.groups;\n const mediaType = type.split(\";\");\n hash = stripHash ? \"\" : hash;\n\n let isBase64 = false;\n if (mediaType[mediaType.length - 1] === \"base64\") {\n mediaType.pop();\n isBase64 = true;\n }\n\n // Lowercase MIME type\n const mimeType = mediaType.shift()?.toLowerCase() ?? \"\";\n const attributes = mediaType\n .map((attribute) => {\n let [key, value = \"\"] = attribute.split(\"=\").map((string) => string.trim());\n\n // Lowercase `charset`\n if (key === \"charset\") {\n value = value.toLowerCase();\n\n if (value === DATA_URL_DEFAULT_CHARSET) {\n return \"\";\n }\n }\n\n return `${key}${value ? `=${value}` : \"\"}`;\n })\n .filter(Boolean);\n\n const normalizedMediaType = [...attributes];\n\n if (isBase64) {\n normalizedMediaType.push(\"base64\");\n }\n\n if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {\n normalizedMediaType.unshift(mimeType);\n }\n\n return `data:${normalizedMediaType.join(\";\")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : \"\"}`;\n};\n\n/**\n * Modified with extra typing 23/02/2025\n * https://github.com/sindresorhus/normalize-url v8.0.1 patched at 12/01/2024\n * v8.0.0 downloaded at 13/08/2023\n * @param urlString\n * @param options\n * @return {*|string}\n */\nexport function normalizeUrl(\n urlString: string,\n options?: Partial<{\n defaultProtocol: \"http\" | \"https\" | \"ws\" | \"wss\" | string;\n normalizeProtocol: boolean;\n forceHttp: boolean;\n forceHttps: boolean;\n stripAuthentication: boolean;\n stripHash: boolean;\n stripTextFragment: boolean;\n stripProtocol: boolean;\n stripWWW: boolean;\n removeQueryParameters: boolean | RegExp[];\n keepQueryParameters: string[];\n removeTrailingSlash: boolean;\n removeSingleSlash: boolean;\n removeDirectoryIndex: boolean | RegExp[];\n removeExplicitPort: boolean;\n sortQueryParameters: boolean;\n }>,\n): string {\n options = {\n defaultProtocol: \"http\",\n normalizeProtocol: true,\n forceHttp: false,\n forceHttps: false,\n stripAuthentication: true,\n stripHash: false,\n stripTextFragment: true,\n stripWWW: true,\n removeQueryParameters: [/^utm_\\w+/i],\n removeTrailingSlash: true,\n removeSingleSlash: true,\n removeDirectoryIndex: false,\n removeExplicitPort: false,\n sortQueryParameters: true,\n ...options,\n };\n\n // Legacy: Append `:` to the protocol if missing.\n if (typeof options.defaultProtocol === \"string\" && !options.defaultProtocol.endsWith(\":\")) {\n options.defaultProtocol = `${options.defaultProtocol}:`;\n }\n\n urlString = urlString.trim();\n\n // Data URL\n if (/^data:/i.test(urlString)) {\n return normalizeDataURL(urlString, { stripHash: options.stripHash });\n }\n\n if (hasCustomProtocol(urlString)) {\n return urlString;\n }\n\n const hasRelativeProtocol = urlString.startsWith(\"//\");\n const isRelativeUrl = !hasRelativeProtocol && /^\\.*\\//.test(urlString);\n\n // Prepend protocol\n if (!isRelativeUrl) {\n urlString = urlString.replace(/^(?!(?:\\w+:)?\\/\\/)|^\\/\\//, options.defaultProtocol ?? defaultHttpProtocol);\n }\n\n const urlObject = new URL(urlString);\n\n if (options.forceHttp && options.forceHttps) {\n throw new Error(\"The `forceHttp` and `forceHttps` options cannot be used together\");\n }\n\n if (options.forceHttp && urlObject.protocol === \"https:\") {\n urlObject.protocol = \"http:\";\n }\n\n if (options.forceHttps && urlObject.protocol === \"http:\") {\n urlObject.protocol = \"https:\";\n }\n\n // Remove auth\n if (options.stripAuthentication) {\n urlObject.username = \"\";\n urlObject.password = \"\";\n }\n\n // Remove hash\n if (options.stripHash) {\n urlObject.hash = \"\";\n } else if (options.stripTextFragment) {\n urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, \"\");\n }\n\n // Remove duplicate slashes if not preceded by a protocol\n // NOTE: This could be implemented using a single negative lookbehind\n // regex, but we avoid that to maintain compatibility with older js engines\n // which do not have support for that feature.\n if (urlObject.pathname) {\n // TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\\b[a-z][a-z\\d+\\-.]{1,50}:)\\/{2,}/g, '/');` when Safari supports negative lookbehind.\n\n // Split the string by occurrences of this protocol regex, and perform\n // duplicate-slash replacement on the strings between those occurrences\n // (if any).\n const protocolRegex = /\\b[a-z][a-z\\d+\\-.]{1,50}:\\/\\//g;\n\n let lastIndex = 0;\n let result = \"\";\n for (;;) {\n const match = protocolRegex.exec(urlObject.pathname);\n if (!match) {\n break;\n }\n\n const protocol = match[0];\n const protocolAtIndex = match.index;\n const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);\n\n result += intermediate.replace(/\\/{2,}/g, \"/\");\n result += protocol;\n lastIndex = protocolAtIndex + protocol.length;\n }\n\n const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);\n result += remnant.replace(/\\/{2,}/g, \"/\");\n\n urlObject.pathname = result;\n }\n\n // Decode URI octets\n if (urlObject.pathname) {\n try {\n urlObject.pathname = decodeURI(urlObject.pathname);\n } catch {}\n }\n\n // Remove directory index\n if (options.removeDirectoryIndex === true) {\n options.removeDirectoryIndex = [/^index\\.[a-z]+$/];\n }\n\n if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {\n let pathComponents = urlObject.pathname.split(\"/\");\n const lastComponent = pathComponents[pathComponents.length - 1];\n\n if (testParameter(lastComponent, options.removeDirectoryIndex)) {\n pathComponents = pathComponents.slice(0, -1);\n urlObject.pathname = pathComponents.slice(1).join(\"/\") + \"/\";\n }\n }\n\n if (urlObject.hostname) {\n // Remove trailing dot\n urlObject.hostname = urlObject.hostname.replace(/\\.$/, \"\");\n\n // Remove `www.`\n if (options.stripWWW && /^www\\.(?!www\\.)[a-z\\-\\d]{1,63}\\.[a-z.\\-\\d]{2,63}$/.test(urlObject.hostname)) {\n // Each label should be max 63 at length (min: 1).\n // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names\n // Each TLD should be up to 63 characters long (min: 2).\n // It is technically possible to have a single character TLD, but none currently exist.\n urlObject.hostname = urlObject.hostname.replace(/^www\\./, \"\");\n }\n }\n\n // Remove query unwanted parameters\n if (Array.isArray(options.removeQueryParameters)) {\n // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.\n for (const key of [...urlObject.searchParams.keys()]) {\n if (testParameter(key, options.removeQueryParameters)) {\n urlObject.searchParams.delete(key);\n }\n }\n }\n\n if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {\n urlObject.search = \"\";\n }\n\n // Keep wanted query parameters\n if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {\n // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.\n for (const key of [...urlObject.searchParams.keys()]) {\n if (!testParameter(key, options.keepQueryParameters)) {\n urlObject.searchParams.delete(key);\n }\n }\n }\n\n // Sort query parameters\n if (options.sortQueryParameters) {\n urlObject.searchParams.sort();\n\n // Calling `.sort()` encodes the search parameters, so we need to decode them again.\n try {\n urlObject.search = decodeURIComponent(urlObject.search);\n } catch {}\n }\n\n if (options.removeTrailingSlash) {\n urlObject.pathname = urlObject.pathname.replace(/\\/$/, \"\");\n }\n\n // Remove an explicit port number, excluding a default port number, if applicable\n if (options.removeExplicitPort && urlObject.port) {\n urlObject.port = \"\";\n }\n\n const oldUrlString = urlString;\n\n // Take advantage of many of the Node `url` normalizations\n urlString = urlObject.toString();\n\n if (\n !options.removeSingleSlash &&\n urlObject.pathname === \"/\" &&\n !oldUrlString.endsWith(\"/\") &&\n urlObject.hash === \"\"\n ) {\n urlString = urlString.replace(/\\/$/, \"\");\n }\n\n // Remove ending `/` unless removeSingleSlash is false\n if (\n (options.removeTrailingSlash || urlObject.pathname === \"/\") &&\n urlObject.hash === \"\" &&\n options.removeSingleSlash\n ) {\n urlString = urlString.replace(/\\/$/, \"\");\n }\n\n // Restore relative protocol, if applicable\n if (hasRelativeProtocol && !options.normalizeProtocol) {\n urlString = urlString.replace(/^http:\\/\\//, \"//\");\n }\n\n // Remove http/https\n if (options.stripProtocol) {\n urlString = urlString.replace(/^(?:https?:)?\\/\\//, \"\");\n }\n\n return urlString;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"normalize-url.js","names":[],"sources":["../../src/utils/normalize-url.ts"],"sourcesContent":["// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs\nimport { defaultHttpProtocol } from \"@/utils/url.utils\";\n\nconst DATA_URL_DEFAULT_MIME_TYPE = \"text/plain\";\nconst DATA_URL_DEFAULT_CHARSET = \"us-ascii\";\n\nconst testParameter = (name: string, filters: (RegExp | any)[]) =>\n filters.some((filter) => (filter instanceof RegExp ? filter.test(name) : filter === name));\n\nconst supportedProtocols = new Set([\"https:\", \"http:\", \"file:\"]);\n\nconst hasCustomProtocol = (urlString: string) => {\n try {\n const { protocol } = new URL(urlString);\n\n return protocol.endsWith(\":\") && !protocol.includes(\".\") && !supportedProtocols.has(protocol);\n } catch {\n return false;\n }\n};\n\nconst normalizeDataURL = (urlString: string, { stripHash }: { stripHash?: boolean }) => {\n const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);\n\n if (!match?.groups) {\n throw new Error(`Invalid URL: ${urlString}`);\n }\n\n let { type, data, hash } = match.groups;\n const mediaType = type.split(\";\");\n hash = stripHash ? \"\" : hash;\n\n let isBase64 = false;\n if (mediaType[mediaType.length - 1] === \"base64\") {\n mediaType.pop();\n isBase64 = true;\n }\n\n // Lowercase MIME type\n const mimeType = mediaType.shift()?.toLowerCase() ?? \"\";\n const attributes = mediaType\n .map((attribute) => {\n let [key, value = \"\"] = attribute.split(\"=\").map((string) => string.trim());\n\n // Lowercase `charset`\n if (key === \"charset\") {\n value = value.toLowerCase();\n\n if (value === DATA_URL_DEFAULT_CHARSET) {\n return \"\";\n }\n }\n\n return `${key}${value ? `=${value}` : \"\"}`;\n })\n .filter(Boolean);\n\n const normalizedMediaType = [...attributes];\n\n if (isBase64) {\n normalizedMediaType.push(\"base64\");\n }\n\n if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {\n normalizedMediaType.unshift(mimeType);\n }\n\n return `data:${normalizedMediaType.join(\";\")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : \"\"}`;\n};\n\n/**\n * Modified with extra typing 23/02/2025\n * https://github.com/sindresorhus/normalize-url v8.0.1 patched at 12/01/2024\n * v8.0.0 downloaded at 13/08/2023\n * @param urlString\n * @param options\n * @return {*|string}\n */\nexport function normalizeUrl(\n urlString: string,\n options?: Partial<{\n defaultProtocol: \"http\" | \"https\" | \"ws\" | \"wss\" | string;\n normalizeProtocol: boolean;\n forceHttp: boolean;\n forceHttps: boolean;\n stripAuthentication: boolean;\n stripHash: boolean;\n stripTextFragment: boolean;\n stripProtocol: boolean;\n stripWWW: boolean;\n removeQueryParameters: boolean | RegExp[];\n keepQueryParameters: string[];\n removeTrailingSlash: boolean;\n removeSingleSlash: boolean;\n removeDirectoryIndex: boolean | RegExp[];\n removeExplicitPort: boolean;\n sortQueryParameters: boolean;\n }>,\n): string {\n options = {\n defaultProtocol: \"http\",\n normalizeProtocol: true,\n forceHttp: false,\n forceHttps: false,\n stripAuthentication: true,\n stripHash: false,\n stripTextFragment: true,\n stripWWW: true,\n removeQueryParameters: [/^utm_\\w+/i],\n removeTrailingSlash: true,\n removeSingleSlash: true,\n removeDirectoryIndex: false,\n removeExplicitPort: false,\n sortQueryParameters: true,\n ...options,\n };\n\n // Legacy: Append `:` to the protocol if missing.\n if (typeof options.defaultProtocol === \"string\" && !options.defaultProtocol.endsWith(\":\")) {\n options.defaultProtocol = `${options.defaultProtocol}:`;\n }\n\n urlString = urlString.trim();\n\n // Data URL\n if (/^data:/i.test(urlString)) {\n return normalizeDataURL(urlString, { stripHash: options.stripHash });\n }\n\n if (hasCustomProtocol(urlString)) {\n return urlString;\n }\n\n const hasRelativeProtocol = urlString.startsWith(\"//\");\n const isRelativeUrl = !hasRelativeProtocol && /^\\.*\\//.test(urlString);\n\n // Prepend protocol\n if (!isRelativeUrl) {\n urlString = urlString.replace(/^(?!(?:\\w+:)?\\/\\/)|^\\/\\//, options.defaultProtocol ?? defaultHttpProtocol);\n }\n\n const urlObject = new URL(urlString);\n\n if (options.forceHttp && options.forceHttps) {\n throw new Error(\"The `forceHttp` and `forceHttps` options cannot be used together\");\n }\n\n if (options.forceHttp && urlObject.protocol === \"https:\") {\n urlObject.protocol = \"http:\";\n }\n\n if (options.forceHttps && urlObject.protocol === \"http:\") {\n urlObject.protocol = \"https:\";\n }\n\n // Remove auth\n if (options.stripAuthentication) {\n urlObject.username = \"\";\n urlObject.password = \"\";\n }\n\n // Remove hash\n if (options.stripHash) {\n urlObject.hash = \"\";\n } else if (options.stripTextFragment) {\n urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, \"\");\n }\n\n // Remove duplicate slashes if not preceded by a protocol\n // NOTE: This could be implemented using a single negative lookbehind\n // regex, but we avoid that to maintain compatibility with older js engines\n // which do not have support for that feature.\n if (urlObject.pathname) {\n // TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\\b[a-z][a-z\\d+\\-.]{1,50}:)\\/{2,}/g, '/');` when Safari supports negative lookbehind.\n\n // Split the string by occurrences of this protocol regex, and perform\n // duplicate-slash replacement on the strings between those occurrences\n // (if any).\n const protocolRegex = /\\b[a-z][a-z\\d+\\-.]{1,50}:\\/\\//g;\n\n let lastIndex = 0;\n let result = \"\";\n for (;;) {\n const match = protocolRegex.exec(urlObject.pathname);\n if (!match) {\n break;\n }\n\n const protocol = match[0];\n const protocolAtIndex = match.index;\n const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);\n\n result += intermediate.replace(/\\/{2,}/g, \"/\");\n result += protocol;\n lastIndex = protocolAtIndex + protocol.length;\n }\n\n const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);\n result += remnant.replace(/\\/{2,}/g, \"/\");\n\n urlObject.pathname = result;\n }\n\n // Decode URI octets\n if (urlObject.pathname) {\n try {\n urlObject.pathname = decodeURI(urlObject.pathname);\n } catch {}\n }\n\n // Remove directory index\n if (options.removeDirectoryIndex === true) {\n options.removeDirectoryIndex = [/^index\\.[a-z]+$/];\n }\n\n if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {\n let pathComponents = urlObject.pathname.split(\"/\");\n const lastComponent = pathComponents[pathComponents.length - 1];\n\n if (testParameter(lastComponent, options.removeDirectoryIndex)) {\n pathComponents = pathComponents.slice(0, -1);\n urlObject.pathname = pathComponents.slice(1).join(\"/\") + \"/\";\n }\n }\n\n if (urlObject.hostname) {\n // Remove trailing dot\n urlObject.hostname = urlObject.hostname.replace(/\\.$/, \"\");\n\n // Remove `www.`\n if (options.stripWWW && /^www\\.(?!www\\.)[a-z\\-\\d]{1,63}\\.[a-z.\\-\\d]{2,63}$/.test(urlObject.hostname)) {\n // Each label should be max 63 at length (min: 1).\n // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names\n // Each TLD should be up to 63 characters long (min: 2).\n // It is technically possible to have a single character TLD, but none currently exist.\n urlObject.hostname = urlObject.hostname.replace(/^www\\./, \"\");\n }\n }\n\n // Remove query unwanted parameters\n if (Array.isArray(options.removeQueryParameters)) {\n // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.\n for (const key of [...urlObject.searchParams.keys()]) {\n if (testParameter(key, options.removeQueryParameters)) {\n urlObject.searchParams.delete(key);\n }\n }\n }\n\n if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {\n urlObject.search = \"\";\n }\n\n // Keep wanted query parameters\n if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {\n // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.\n for (const key of [...urlObject.searchParams.keys()]) {\n if (!testParameter(key, options.keepQueryParameters)) {\n urlObject.searchParams.delete(key);\n }\n }\n }\n\n // Sort query parameters\n if (options.sortQueryParameters) {\n urlObject.searchParams.sort();\n\n // Calling `.sort()` encodes the search parameters, so we need to decode them again.\n try {\n urlObject.search = decodeURIComponent(urlObject.search);\n } catch {}\n }\n\n if (options.removeTrailingSlash) {\n urlObject.pathname = urlObject.pathname.replace(/\\/$/, \"\");\n }\n\n // Remove an explicit port number, excluding a default port number, if applicable\n if (options.removeExplicitPort && urlObject.port) {\n urlObject.port = \"\";\n }\n\n const oldUrlString = urlString;\n\n // Take advantage of many of the Node `url` normalizations\n urlString = urlObject.toString();\n\n if (\n !options.removeSingleSlash &&\n urlObject.pathname === \"/\" &&\n !oldUrlString.endsWith(\"/\") &&\n urlObject.hash === \"\"\n ) {\n urlString = urlString.replace(/\\/$/, \"\");\n }\n\n // Remove ending `/` unless removeSingleSlash is false\n if (\n (options.removeTrailingSlash || urlObject.pathname === \"/\") &&\n urlObject.hash === \"\" &&\n options.removeSingleSlash\n ) {\n urlString = urlString.replace(/\\/$/, \"\");\n }\n\n // Restore relative protocol, if applicable\n if (hasRelativeProtocol && !options.normalizeProtocol) {\n urlString = urlString.replace(/^http:\\/\\//, \"//\");\n }\n\n // Remove http/https\n if (options.stripProtocol) {\n urlString = urlString.replace(/^(?:https?:)?\\/\\//, \"\");\n }\n\n return urlString;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"3mf.parser.js","names":["fs","path"],"sources":["../../../src/utils/parsers/3mf.parser.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport AdmZip from \"adm-zip\";\nimport { ThreeMFMetadata } from \"@/entities/print-job.entity\";\nimport { ParsedThumbnail } from \"./parser.types\";\nimport { getImageDimensions } from \"../image-dimensions\";\n\ninterface ThreeMFParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n plates?: ThreeMFMetadata[\"plates\"];\n };\n normalized: ThreeMFMetadata;\n plates?: ThreeMFMetadata[\"plates\"];\n}\n\n/**\n * 3MF parser for extracting metadata from .3mf files\n * Supports both single and multi-plate 3MF files (Bambu Lab format)\n */\nexport class ThreeMFParser {\n async parse(filePath: string): Promise<ThreeMFParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = path.basename(filePath);\n\n const zip = new AdmZip(filePath);\n const zipEntries = zip.getEntries();\n\n // Check for metadata.json first (some slicers may use this)\n const metadataJsonEntry = zipEntries.find((e) => e.entryName === \"metadata.json\");\n let metadata: Record<string, any> = {};\n\n if (metadataJsonEntry) {\n // Parse JSON metadata\n const jsonContent = metadataJsonEntry.getData().toString(\"utf8\");\n const jsonData = JSON.parse(jsonContent);\n metadata = this.normalizeJsonMetadata(jsonData);\n } else {\n // Extract metadata from 3D model XML\n const modelEntry = zipEntries.find(\n (e) => e.entryName === \"3D/3dmodel.model\" || e.entryName === \"Metadata/model_settings.config\",\n );\n metadata = modelEntry ? this.extractMetadataFromXML(modelEntry.getData().toString(\"utf8\")) : {};\n }\n\n // Check for multi-plate structure (Bambu Lab)\n const plates = this.extractPlates(zipEntries);\n const isMultiPlate = plates.length > 1;\n\n // Extract thumbnails\n const thumbnails = this.extractThumbnails(zipEntries);\n\n // For single-plate files, use plate data for top-level metadata\n let topLevelPrintTime = this.parseTime(metadata.printTime);\n let topLevelFilamentWeight = this.parseFloat(metadata.totalFilamentWeight || metadata.filamentWeight);\n let topLevelLayers = this.parseInt(metadata.layerCount);\n let topLevelFilamentUsedMm = this.parseFloat(metadata.filamentUsed);\n let topLevelFilamentUsedCm3 = this.parseFloat(metadata.filamentVolume);\n let topLevelFilamentDensity = this.parseFloat(metadata.filamentDensity);\n let topLevelMaxZ = this.parseFloat(metadata.maxZ);\n let topLevelSlicerVersion = metadata.slicerVersion || metadata.generator;\n\n // Additional fields from plates\n let topLevelNozzleDiameter = this.parseFloat(metadata.nozzleDiameter);\n let topLevelLayerHeight = this.parseFloat(metadata.layerHeight || metadata.layer_height);\n let topLevelFirstLayerHeight = this.parseFloat(metadata.firstLayerHeight || metadata.first_layer_height);\n let topLevelBedTemp = this.parseFloat(metadata.bedTemp || metadata.bed_temperature);\n let topLevelNozzleTemp = this.parseFloat(metadata.nozzleTemp || metadata.nozzle_temperature);\n let topLevelFillDensity = metadata.infillDensity || metadata.infill_density || metadata.fill_density || null;\n let topLevelFilamentType = metadata.filamentType || metadata.filament_type || null;\n let topLevelPrinterModel = metadata.printerModel || metadata.printer_model || null;\n let topLevelFilamentDiameter = this.parseFloat(metadata.filamentDiameter || metadata.filament_diameter) || 1.75;\n\n if (plates.length >= 1 && plates[0]) {\n if (plates.length === 1) {\n // Single plate - promote ALL plate data to top level\n const plate = plates[0] as any;\n topLevelPrintTime = plate.gcodePrintTimeSeconds ?? topLevelPrintTime;\n topLevelFilamentWeight = plate.filamentUsedGrams ?? topLevelFilamentWeight;\n topLevelLayers = plate.totalLayers ?? topLevelLayers;\n topLevelFilamentUsedMm = plate.filamentUsedMm ?? topLevelFilamentUsedMm;\n topLevelFilamentUsedCm3 = plate.filamentUsedCm3 ?? topLevelFilamentUsedCm3;\n topLevelFilamentDensity = plate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;\n topLevelMaxZ = plate.maxLayerZ ?? topLevelMaxZ;\n topLevelSlicerVersion = plate.slicerVersion ?? topLevelSlicerVersion;\n topLevelNozzleDiameter = plate.nozzleDiameterMm ?? topLevelNozzleDiameter;\n topLevelLayerHeight = plate.layerHeight ?? topLevelLayerHeight;\n topLevelFirstLayerHeight = plate.firstLayerHeight ?? topLevelFirstLayerHeight;\n topLevelBedTemp = plate.bedTemperature ?? topLevelBedTemp;\n topLevelNozzleTemp = plate.nozzleTemperature ?? topLevelNozzleTemp;\n topLevelFillDensity = plate.fillDensity ?? topLevelFillDensity;\n topLevelFilamentType = plate.filamentType ?? topLevelFilamentType;\n topLevelPrinterModel = plate.printerModel ?? topLevelPrinterModel;\n topLevelFilamentDiameter = plate.filamentDiameterMm ?? topLevelFilamentDiameter;\n } else {\n // Multi-plate - aggregate data from all plates\n topLevelPrintTime = plates.reduce((sum, p: any) => sum + (p.gcodePrintTimeSeconds || 0), 0);\n topLevelFilamentWeight = plates.reduce((sum, p: any) => sum + (p.filamentUsedGrams || 0), 0);\n topLevelFilamentUsedMm = plates.reduce((sum, p: any) => sum + (p.filamentUsedMm || 0), 0);\n topLevelFilamentUsedCm3 = plates.reduce((sum, p: any) => sum + (p.filamentUsedCm3 || 0), 0);\n topLevelLayers = Math.max(...plates.map((p: any) => p.totalLayers || 0));\n topLevelMaxZ = Math.max(...plates.map((p: any) => p.maxLayerZ || 0));\n\n // Use first plate for shared settings (all plates have same printer/material settings)\n const firstPlate = plates[0] as any;\n topLevelFilamentDensity = firstPlate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;\n topLevelSlicerVersion = firstPlate.slicerVersion ?? topLevelSlicerVersion;\n topLevelNozzleDiameter = firstPlate.nozzleDiameterMm ?? topLevelNozzleDiameter;\n topLevelLayerHeight = firstPlate.layerHeight ?? topLevelLayerHeight;\n topLevelFirstLayerHeight = firstPlate.firstLayerHeight ?? topLevelFirstLayerHeight;\n topLevelBedTemp = firstPlate.bedTemperature ?? topLevelBedTemp;\n topLevelNozzleTemp = firstPlate.nozzleTemperature ?? topLevelNozzleTemp;\n topLevelFillDensity = firstPlate.fillDensity ?? topLevelFillDensity;\n topLevelFilamentType = firstPlate.filamentType ?? topLevelFilamentType;\n topLevelPrinterModel = firstPlate.printerModel ?? topLevelPrinterModel;\n topLevelFilamentDiameter = firstPlate.filamentDiameterMm ?? topLevelFilamentDiameter;\n }\n }\n\n const normalized: ThreeMFMetadata = {\n fileName,\n fileFormat: \"3mf\",\n fileSize: stats.size,\n isMultiPlate,\n totalPlates: plates.length || 1,\n gcodePrintTimeSeconds: topLevelPrintTime,\n nozzleDiameterMm: topLevelNozzleDiameter,\n filamentDiameterMm: topLevelFilamentDiameter,\n filamentDensityGramsCm3: topLevelFilamentDensity,\n filamentUsedMm: topLevelFilamentUsedMm,\n filamentUsedCm3: topLevelFilamentUsedCm3,\n filamentUsedGrams: topLevelFilamentWeight,\n totalFilamentUsedGrams: topLevelFilamentWeight,\n layerHeight: topLevelLayerHeight,\n firstLayerHeight: topLevelFirstLayerHeight,\n bedTemperature: topLevelBedTemp,\n nozzleTemperature: topLevelNozzleTemp,\n fillDensity: topLevelFillDensity,\n filamentType: topLevelFilamentType,\n printerModel: topLevelPrinterModel,\n slicerVersion: topLevelSlicerVersion,\n maxLayerZ: topLevelMaxZ,\n totalLayers: topLevelLayers,\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n plates: plates.length > 0 ? plates : undefined,\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n plates: plates.length > 0 ? plates : undefined,\n },\n normalized,\n plates: plates.length > 0 ? plates : undefined,\n };\n }\n\n private normalizeJsonMetadata(jsonData: any): Record<string, any> {\n // Normalize JSON metadata keys to match expected format\n const metadata: Record<string, any> = {};\n\n // Map common JSON metadata keys to our internal format\n if (jsonData.nozzleDiameter !== undefined) metadata.nozzleDiameter = String(jsonData.nozzleDiameter);\n if (jsonData.estimatedPrintTimeSec !== undefined) metadata.printTime = String(jsonData.estimatedPrintTimeSec);\n if (jsonData.filamentDiameter !== undefined) metadata.filamentDiameter = String(jsonData.filamentDiameter);\n if (jsonData.filamentDensity !== undefined) metadata.filamentDensity = String(jsonData.filamentDensity);\n if (jsonData.filamentUsedGrams !== undefined) metadata.filamentWeight = String(jsonData.filamentUsedGrams);\n if (jsonData.layerHeight !== undefined) metadata.layerHeight = String(jsonData.layerHeight);\n if (jsonData.firstLayerHeight !== undefined) metadata.firstLayerHeight = String(jsonData.firstLayerHeight);\n if (jsonData.bedTemp !== undefined) metadata.bedTemp = String(jsonData.bedTemp);\n if (jsonData.nozzleTemp !== undefined) metadata.nozzleTemp = String(jsonData.nozzleTemp);\n if (jsonData.fillDensity !== undefined) metadata.infillDensity = String(jsonData.fillDensity);\n if (jsonData.filamentType !== undefined) metadata.filamentType = jsonData.filamentType;\n if (jsonData.printerModel !== undefined) metadata.printerModel = jsonData.printerModel;\n\n return metadata;\n }\n\n private extractMetadataFromXML(xml: string): Record<string, string> {\n const metadata: Record<string, string> = {};\n\n // Extract simple key-value pairs from XML\n const patterns = [\n /<printtime>([^<]+)<\\/printtime>/i,\n /<layerheight>([^<]+)<\\/layerheight>/i,\n /<filamentused>([^<]+)<\\/filamentused>/i,\n /<filamenttype>([^<]+)<\\/filamenttype>/i,\n /<nozzlediameter>([^<]+)<\\/nozzlediameter>/i,\n /<bedtemperature>([^<]+)<\\/bedtemperature>/i,\n /<nozzletemperature>([^<]+)<\\/nozzletemperature>/i,\n ];\n\n for (const pattern of patterns) {\n const match = xml.match(pattern);\n if (match) {\n const key = pattern.source.match(/<([^>]+)>/)?.[1] || \"\";\n metadata[key] = match[1];\n }\n }\n\n // Extract generator/slicer info\n const generatorMatch = xml.match(/generator=\"([^\"]+)\"/);\n if (generatorMatch) {\n metadata.generator = generatorMatch[1];\n metadata.slicerVersion = generatorMatch[1];\n }\n\n return metadata;\n }\n\n private extractPlates(zipEntries: AdmZip.IZipEntry[]): NonNullable<ThreeMFMetadata[\"plates\"]> {\n const plates: NonNullable<ThreeMFMetadata[\"plates\"]> = [];\n\n // Look for Bambu Lab plate structure (but not .gcode.md5 files)\n const plateEntries = zipEntries.filter(\n (e) => e.entryName.match(/Metadata\\/plate_\\d+\\.gcode$/) && !e.entryName.endsWith(\".md5\"),\n );\n\n if (plateEntries.length === 0) {\n // Single plate file or non-Bambu format\n return [];\n }\n\n for (const entry of plateEntries) {\n const plateMatch = entry.entryName.match(/plate_(\\d+)\\.gcode/);\n if (!plateMatch) continue;\n\n // Bambu uses 1-indexed plate numbers in filenames (plate_1.gcode = plate 1)\n const plateNumber = Number.parseInt(plateMatch[1]);\n // Read more bytes to include CONFIG_BLOCK (contains layer_height, temps, etc.)\n const gcodeContent = entry.getData().toString(\"utf8\", 0, Math.min(50000, entry.getData().length));\n\n // Parse basic metadata from G-code header\n const metadata = this.parseGCodeHeader(gcodeContent);\n\n // Find thumbnails for this plate\n const plateThumbs = zipEntries.filter(\n (e) =>\n e.entryName.includes(`plate_${plateMatch[1]}`) &&\n (e.entryName.endsWith(\".png\") || e.entryName.endsWith(\".jpg\")),\n );\n\n const plateThumbnails: ParsedThumbnail[] = plateThumbs.map((t) => {\n const format = t.entryName.endsWith(\".png\") ? \"PNG\" : \"JPG\";\n const imageData = t.getData();\n\n const sizeMatch = t.entryName.match(/(\\d+)x(\\d+)/);\n let width = sizeMatch ? Number.parseInt(sizeMatch[1]) : 0;\n let height = sizeMatch ? Number.parseInt(sizeMatch[2]) : 0;\n\n if (width === 0 || height === 0) {\n const dimensions = getImageDimensions(imageData, format);\n width = dimensions.width;\n height = dimensions.height;\n }\n\n return {\n width,\n height,\n format,\n data: imageData.toString(\"base64\"),\n };\n });\n const printTime = this.parseTime(\n metadata.model_printing_time || metadata.total_estimated_time || metadata.print_time,\n );\n const filamentWeight = this.parseFloat(\n metadata.total_filament_weight_g || metadata.filament_weight || metadata.total_filament_weight,\n );\n const layerCount = this.parseInt(metadata.total_layer_number || metadata.layer_count || metadata.total_layers);\n\n // Extract all available Bambu metadata from G-code header + config\n const plateMetadata = {\n plateNumber,\n gcodePrintTimeSeconds: printTime,\n filamentUsedGrams: filamentWeight,\n totalLayers: layerCount,\n filamentUsedMm: this.parseFloat(\n metadata.total_filament_length_mm || metadata.filament_length_mm || metadata.filament_used_mm,\n ),\n filamentUsedCm3: this.parseFloat(metadata.total_filament_volume_cm3 || metadata.filament_volume_cm3),\n filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),\n filamentDiameterMm: this.parseFloat(metadata.filament_diameter) || 1.75,\n maxLayerZ: this.parseFloat(metadata.max_z_height || metadata.max_layer_z),\n slicerVersion: metadata.bambustudio || metadata.slicer_version || metadata.slicer || null,\n nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(\n metadata.first_layer_height || metadata.initial_layer_height || metadata.initial_layer_print_height,\n ),\n bedTemperature: this.parseFloat(\n metadata.bed_temperature_actual ||\n metadata.bed_temperature ||\n metadata.bed_temp ||\n metadata.bed_temperature_initial_layer,\n ),\n nozzleTemperature: this.parseFloat(\n metadata.nozzle_temperature || metadata.nozzle_temp || metadata.nozzle_temperature_initial_layer,\n ),\n fillDensity: metadata.sparse_infill_density || metadata.infill_density || metadata.fill_density || null,\n filamentType: metadata.filament_type || null,\n printerModel: metadata.printer_model || null,\n objects: [],\n thumbnails:\n plateThumbnails.length > 0\n ? plateThumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n };\n\n plates.push(plateMetadata);\n }\n\n return plates.sort((a, b) => a.plateNumber - b.plateNumber);\n }\n\n private parseGCodeHeader(gcode: string): Record<string, string> {\n const metadata: Record<string, string> = {};\n const lines = gcode.split(\"\\n\").slice(0, 1000); // Extended to include CONFIG_BLOCK and early G-code\n\n for (const line of lines) {\n // Parse comment lines\n if (line.startsWith(\";\")) {\n // Skip block markers\n if (line.includes(\"_BLOCK_START\") || line.includes(\"_BLOCK_END\")) continue;\n\n // Special case: BambuStudio version (no key:value pattern)\n // ; BambuStudio 02.04.00.70\n const bambuMatch = line.match(/;\\s*BambuStudio\\s+([\\d.]+)/i);\n if (bambuMatch) {\n metadata.bambustudio = `BambuStudio ${bambuMatch[1]}`;\n continue;\n }\n\n // Match patterns like:\n // ; key: value\n // ; key = value\n // ; key : value\n const match = line.match(/;\\s*([^=:]+?)\\s*[:=]\\s*(.+)/);\n if (match) {\n let key = match[1].trim().toLowerCase().replace(/\\s+/g, \"_\");\n let value = match[2].trim();\n\n // Remove bracketed units from key: \"total_filament_weight_[g]\" -> \"total_filament_weight_g\"\n // Also normalize special chars: [cm^3] -> cm3\n key = key.replace(/\\[([^\\]]+)\\]/g, (_, unit) => unit.replace(/\\^/g, \"\"));\n\n // Split value on semicolon and take first part (handles \"11m 15s; total estimated time: 18m 15s\")\n value = value.split(\";\")[0].trim();\n\n // Remove remaining bracketed units and percentages from value\n value = value.replace(/\\s*\\[.*?\\]\\s*/g, \"\"); // Remove [units]\n\n // Store the value (don't overwrite if already set from header)\n if (!metadata[key]) {\n metadata[key] = value;\n }\n }\n } else {\n // Parse actual G-code commands for temperatures\n // M140 S65 - Set bed temperature\n // M190 S65 - Wait for bed temperature\n if (!metadata.bed_temperature_actual) {\n const bedTempMatch = line.match(/^M1(40|90)\\s+S(\\d+)/);\n if (bedTempMatch && Number.parseInt(bedTempMatch[2]) > 0) {\n metadata.bed_temperature_actual = bedTempMatch[2];\n }\n }\n }\n }\n\n return metadata;\n }\n\n private extractThumbnails(zipEntries: AdmZip.IZipEntry[]): ParsedThumbnail[] {\n const thumbnails: ParsedThumbnail[] = [];\n\n const thumbEntries = zipEntries.filter(\n (e) => e.entryName.match(/Metadata\\/.*\\.(png|jpg|jpeg)/i) || e.entryName.match(/Thumbnails\\/.*/i),\n );\n\n for (const entry of thumbEntries) {\n const format = entry.entryName.match(/\\.(png|jpg|jpeg)$/i)?.[1].toUpperCase() || \"PNG\";\n const imageData = entry.getData();\n\n const sizeMatch = entry.entryName.match(/(\\d+)x(\\d+)/);\n let width = sizeMatch ? Number.parseInt(sizeMatch[1]) : 0;\n let height = sizeMatch ? Number.parseInt(sizeMatch[2]) : 0;\n\n if (width === 0 || height === 0) {\n const dimensions = getImageDimensions(imageData, format);\n width = dimensions.width;\n height = dimensions.height;\n }\n\n const base64Data = imageData.toString(\"base64\");\n\n thumbnails.push({\n width,\n height,\n format,\n data: base64Data,\n });\n }\n\n return thumbnails;\n }\n\n private parseFloat(value: string | undefined): number | null {\n if (!value) return null;\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseInt(value: string | undefined): number | null {\n if (!value) return null;\n const num = Number.parseInt(value, 10);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n // Try parsing as a duration string first (e.g., \"11m 15s\" or \"1h 30m\")\n const match = value.match(/(?:(\\d+)h)?(?:\\s*(\\d+)m)?(?:\\s*(\\d+)s)?/);\n if (match && (match[1] || match[2] || match[3])) {\n const hours = Number.parseInt(match[1] || \"0\");\n const minutes = Number.parseInt(match[2] || \"0\");\n const secs = Number.parseInt(match[3] || \"0\");\n return hours * 3600 + minutes * 60 + secs;\n }\n\n // Fallback to parsing as plain seconds\n const seconds = Number.parseFloat(value);\n if (!Number.isNaN(seconds)) return seconds;\n\n return null;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"3mf.parser.js","names":["fs","path"],"sources":["../../../src/utils/parsers/3mf.parser.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport AdmZip from \"adm-zip\";\nimport { ThreeMFMetadata } from \"@/entities/print-job.entity\";\nimport { ParsedThumbnail } from \"./parser.types\";\nimport { getImageDimensions } from \"../image-dimensions\";\n\ninterface ThreeMFParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n plates?: ThreeMFMetadata[\"plates\"];\n };\n normalized: ThreeMFMetadata;\n plates?: ThreeMFMetadata[\"plates\"];\n}\n\n/**\n * 3MF parser for extracting metadata from .3mf files\n * Supports both single and multi-plate 3MF files (Bambu Lab format)\n */\nexport class ThreeMFParser {\n async parse(filePath: string): Promise<ThreeMFParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = path.basename(filePath);\n\n const zip = new AdmZip(filePath);\n const zipEntries = zip.getEntries();\n\n // Check for metadata.json first (some slicers may use this)\n const metadataJsonEntry = zipEntries.find((e) => e.entryName === \"metadata.json\");\n let metadata: Record<string, any> = {};\n\n if (metadataJsonEntry) {\n // Parse JSON metadata\n const jsonContent = metadataJsonEntry.getData().toString(\"utf8\");\n const jsonData = JSON.parse(jsonContent);\n metadata = this.normalizeJsonMetadata(jsonData);\n } else {\n // Extract metadata from 3D model XML\n const modelEntry = zipEntries.find(\n (e) => e.entryName === \"3D/3dmodel.model\" || e.entryName === \"Metadata/model_settings.config\",\n );\n metadata = modelEntry ? this.extractMetadataFromXML(modelEntry.getData().toString(\"utf8\")) : {};\n }\n\n // Check for multi-plate structure (Bambu Lab)\n const plates = this.extractPlates(zipEntries);\n const isMultiPlate = plates.length > 1;\n\n // Extract thumbnails\n const thumbnails = this.extractThumbnails(zipEntries);\n\n // For single-plate files, use plate data for top-level metadata\n let topLevelPrintTime = this.parseTime(metadata.printTime);\n let topLevelFilamentWeight = this.parseFloat(metadata.totalFilamentWeight || metadata.filamentWeight);\n let topLevelLayers = this.parseInt(metadata.layerCount);\n let topLevelFilamentUsedMm = this.parseFloat(metadata.filamentUsed);\n let topLevelFilamentUsedCm3 = this.parseFloat(metadata.filamentVolume);\n let topLevelFilamentDensity = this.parseFloat(metadata.filamentDensity);\n let topLevelMaxZ = this.parseFloat(metadata.maxZ);\n let topLevelSlicerVersion = metadata.slicerVersion || metadata.generator;\n\n // Additional fields from plates\n let topLevelNozzleDiameter = this.parseFloat(metadata.nozzleDiameter);\n let topLevelLayerHeight = this.parseFloat(metadata.layerHeight || metadata.layer_height);\n let topLevelFirstLayerHeight = this.parseFloat(metadata.firstLayerHeight || metadata.first_layer_height);\n let topLevelBedTemp = this.parseFloat(metadata.bedTemp || metadata.bed_temperature);\n let topLevelNozzleTemp = this.parseFloat(metadata.nozzleTemp || metadata.nozzle_temperature);\n let topLevelFillDensity = metadata.infillDensity || metadata.infill_density || metadata.fill_density || null;\n let topLevelFilamentType = metadata.filamentType || metadata.filament_type || null;\n let topLevelPrinterModel = metadata.printerModel || metadata.printer_model || null;\n let topLevelFilamentDiameter = this.parseFloat(metadata.filamentDiameter || metadata.filament_diameter) || 1.75;\n\n if (plates.length >= 1 && plates[0]) {\n if (plates.length === 1) {\n // Single plate - promote ALL plate data to top level\n const plate = plates[0] as any;\n topLevelPrintTime = plate.gcodePrintTimeSeconds ?? topLevelPrintTime;\n topLevelFilamentWeight = plate.filamentUsedGrams ?? topLevelFilamentWeight;\n topLevelLayers = plate.totalLayers ?? topLevelLayers;\n topLevelFilamentUsedMm = plate.filamentUsedMm ?? topLevelFilamentUsedMm;\n topLevelFilamentUsedCm3 = plate.filamentUsedCm3 ?? topLevelFilamentUsedCm3;\n topLevelFilamentDensity = plate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;\n topLevelMaxZ = plate.maxLayerZ ?? topLevelMaxZ;\n topLevelSlicerVersion = plate.slicerVersion ?? topLevelSlicerVersion;\n topLevelNozzleDiameter = plate.nozzleDiameterMm ?? topLevelNozzleDiameter;\n topLevelLayerHeight = plate.layerHeight ?? topLevelLayerHeight;\n topLevelFirstLayerHeight = plate.firstLayerHeight ?? topLevelFirstLayerHeight;\n topLevelBedTemp = plate.bedTemperature ?? topLevelBedTemp;\n topLevelNozzleTemp = plate.nozzleTemperature ?? topLevelNozzleTemp;\n topLevelFillDensity = plate.fillDensity ?? topLevelFillDensity;\n topLevelFilamentType = plate.filamentType ?? topLevelFilamentType;\n topLevelPrinterModel = plate.printerModel ?? topLevelPrinterModel;\n topLevelFilamentDiameter = plate.filamentDiameterMm ?? topLevelFilamentDiameter;\n } else {\n // Multi-plate - aggregate data from all plates\n topLevelPrintTime = plates.reduce((sum, p: any) => sum + (p.gcodePrintTimeSeconds || 0), 0);\n topLevelFilamentWeight = plates.reduce((sum, p: any) => sum + (p.filamentUsedGrams || 0), 0);\n topLevelFilamentUsedMm = plates.reduce((sum, p: any) => sum + (p.filamentUsedMm || 0), 0);\n topLevelFilamentUsedCm3 = plates.reduce((sum, p: any) => sum + (p.filamentUsedCm3 || 0), 0);\n topLevelLayers = Math.max(...plates.map((p: any) => p.totalLayers || 0));\n topLevelMaxZ = Math.max(...plates.map((p: any) => p.maxLayerZ || 0));\n\n // Use first plate for shared settings (all plates have same printer/material settings)\n const firstPlate = plates[0] as any;\n topLevelFilamentDensity = firstPlate.filamentDensityGramsCm3 ?? topLevelFilamentDensity;\n topLevelSlicerVersion = firstPlate.slicerVersion ?? topLevelSlicerVersion;\n topLevelNozzleDiameter = firstPlate.nozzleDiameterMm ?? topLevelNozzleDiameter;\n topLevelLayerHeight = firstPlate.layerHeight ?? topLevelLayerHeight;\n topLevelFirstLayerHeight = firstPlate.firstLayerHeight ?? topLevelFirstLayerHeight;\n topLevelBedTemp = firstPlate.bedTemperature ?? topLevelBedTemp;\n topLevelNozzleTemp = firstPlate.nozzleTemperature ?? topLevelNozzleTemp;\n topLevelFillDensity = firstPlate.fillDensity ?? topLevelFillDensity;\n topLevelFilamentType = firstPlate.filamentType ?? topLevelFilamentType;\n topLevelPrinterModel = firstPlate.printerModel ?? topLevelPrinterModel;\n topLevelFilamentDiameter = firstPlate.filamentDiameterMm ?? topLevelFilamentDiameter;\n }\n }\n\n const normalized: ThreeMFMetadata = {\n fileName,\n fileFormat: \"3mf\",\n fileSize: stats.size,\n isMultiPlate,\n totalPlates: plates.length || 1,\n gcodePrintTimeSeconds: topLevelPrintTime,\n nozzleDiameterMm: topLevelNozzleDiameter,\n filamentDiameterMm: topLevelFilamentDiameter,\n filamentDensityGramsCm3: topLevelFilamentDensity,\n filamentUsedMm: topLevelFilamentUsedMm,\n filamentUsedCm3: topLevelFilamentUsedCm3,\n filamentUsedGrams: topLevelFilamentWeight,\n totalFilamentUsedGrams: topLevelFilamentWeight,\n layerHeight: topLevelLayerHeight,\n firstLayerHeight: topLevelFirstLayerHeight,\n bedTemperature: topLevelBedTemp,\n nozzleTemperature: topLevelNozzleTemp,\n fillDensity: topLevelFillDensity,\n filamentType: topLevelFilamentType,\n printerModel: topLevelPrinterModel,\n slicerVersion: topLevelSlicerVersion,\n maxLayerZ: topLevelMaxZ,\n totalLayers: topLevelLayers,\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n plates: plates.length > 0 ? plates : undefined,\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n plates: plates.length > 0 ? plates : undefined,\n },\n normalized,\n plates: plates.length > 0 ? plates : undefined,\n };\n }\n\n private normalizeJsonMetadata(jsonData: any): Record<string, any> {\n // Normalize JSON metadata keys to match expected format\n const metadata: Record<string, any> = {};\n\n // Map common JSON metadata keys to our internal format\n if (jsonData.nozzleDiameter !== undefined) metadata.nozzleDiameter = String(jsonData.nozzleDiameter);\n if (jsonData.estimatedPrintTimeSec !== undefined) metadata.printTime = String(jsonData.estimatedPrintTimeSec);\n if (jsonData.filamentDiameter !== undefined) metadata.filamentDiameter = String(jsonData.filamentDiameter);\n if (jsonData.filamentDensity !== undefined) metadata.filamentDensity = String(jsonData.filamentDensity);\n if (jsonData.filamentUsedGrams !== undefined) metadata.filamentWeight = String(jsonData.filamentUsedGrams);\n if (jsonData.layerHeight !== undefined) metadata.layerHeight = String(jsonData.layerHeight);\n if (jsonData.firstLayerHeight !== undefined) metadata.firstLayerHeight = String(jsonData.firstLayerHeight);\n if (jsonData.bedTemp !== undefined) metadata.bedTemp = String(jsonData.bedTemp);\n if (jsonData.nozzleTemp !== undefined) metadata.nozzleTemp = String(jsonData.nozzleTemp);\n if (jsonData.fillDensity !== undefined) metadata.infillDensity = String(jsonData.fillDensity);\n if (jsonData.filamentType !== undefined) metadata.filamentType = jsonData.filamentType;\n if (jsonData.printerModel !== undefined) metadata.printerModel = jsonData.printerModel;\n\n return metadata;\n }\n\n private extractMetadataFromXML(xml: string): Record<string, string> {\n const metadata: Record<string, string> = {};\n\n // Extract simple key-value pairs from XML\n const patterns = [\n /<printtime>([^<]+)<\\/printtime>/i,\n /<layerheight>([^<]+)<\\/layerheight>/i,\n /<filamentused>([^<]+)<\\/filamentused>/i,\n /<filamenttype>([^<]+)<\\/filamenttype>/i,\n /<nozzlediameter>([^<]+)<\\/nozzlediameter>/i,\n /<bedtemperature>([^<]+)<\\/bedtemperature>/i,\n /<nozzletemperature>([^<]+)<\\/nozzletemperature>/i,\n ];\n\n for (const pattern of patterns) {\n const match = xml.match(pattern);\n if (match) {\n const key = pattern.source.match(/<([^>]+)>/)?.[1] || \"\";\n metadata[key] = match[1];\n }\n }\n\n // Extract generator/slicer info\n const generatorMatch = xml.match(/generator=\"([^\"]+)\"/);\n if (generatorMatch) {\n metadata.generator = generatorMatch[1];\n metadata.slicerVersion = generatorMatch[1];\n }\n\n return metadata;\n }\n\n private extractPlates(zipEntries: AdmZip.IZipEntry[]): NonNullable<ThreeMFMetadata[\"plates\"]> {\n const plates: NonNullable<ThreeMFMetadata[\"plates\"]> = [];\n\n // Look for Bambu Lab plate structure (but not .gcode.md5 files)\n const plateEntries = zipEntries.filter(\n (e) => e.entryName.match(/Metadata\\/plate_\\d+\\.gcode$/) && !e.entryName.endsWith(\".md5\"),\n );\n\n if (plateEntries.length === 0) {\n // Single plate file or non-Bambu format\n return [];\n }\n\n for (const entry of plateEntries) {\n const plateMatch = entry.entryName.match(/plate_(\\d+)\\.gcode/);\n if (!plateMatch) continue;\n\n // Bambu uses 1-indexed plate numbers in filenames (plate_1.gcode = plate 1)\n const plateNumber = Number.parseInt(plateMatch[1]);\n // Read more bytes to include CONFIG_BLOCK (contains layer_height, temps, etc.)\n const gcodeContent = entry.getData().toString(\"utf8\", 0, Math.min(50000, entry.getData().length));\n\n // Parse basic metadata from G-code header\n const metadata = this.parseGCodeHeader(gcodeContent);\n\n // Find thumbnails for this plate\n const plateThumbs = zipEntries.filter(\n (e) =>\n e.entryName.includes(`plate_${plateMatch[1]}`) &&\n (e.entryName.endsWith(\".png\") || e.entryName.endsWith(\".jpg\")),\n );\n\n const plateThumbnails: ParsedThumbnail[] = plateThumbs.map((t) => {\n const format = t.entryName.endsWith(\".png\") ? \"PNG\" : \"JPG\";\n const imageData = t.getData();\n\n const sizeMatch = t.entryName.match(/(\\d+)x(\\d+)/);\n let width = sizeMatch ? Number.parseInt(sizeMatch[1]) : 0;\n let height = sizeMatch ? Number.parseInt(sizeMatch[2]) : 0;\n\n if (width === 0 || height === 0) {\n const dimensions = getImageDimensions(imageData, format);\n width = dimensions.width;\n height = dimensions.height;\n }\n\n return {\n width,\n height,\n format,\n data: imageData.toString(\"base64\"),\n };\n });\n const printTime = this.parseTime(\n metadata.model_printing_time || metadata.total_estimated_time || metadata.print_time,\n );\n const filamentWeight = this.parseFloat(\n metadata.total_filament_weight_g || metadata.filament_weight || metadata.total_filament_weight,\n );\n const layerCount = this.parseInt(metadata.total_layer_number || metadata.layer_count || metadata.total_layers);\n\n // Extract all available Bambu metadata from G-code header + config\n const plateMetadata = {\n plateNumber,\n gcodePrintTimeSeconds: printTime,\n filamentUsedGrams: filamentWeight,\n totalLayers: layerCount,\n filamentUsedMm: this.parseFloat(\n metadata.total_filament_length_mm || metadata.filament_length_mm || metadata.filament_used_mm,\n ),\n filamentUsedCm3: this.parseFloat(metadata.total_filament_volume_cm3 || metadata.filament_volume_cm3),\n filamentDensityGramsCm3: this.parseFloat(metadata.filament_density),\n filamentDiameterMm: this.parseFloat(metadata.filament_diameter) || 1.75,\n maxLayerZ: this.parseFloat(metadata.max_z_height || metadata.max_layer_z),\n slicerVersion: metadata.bambustudio || metadata.slicer_version || metadata.slicer || null,\n nozzleDiameterMm: this.parseFloat(metadata.nozzle_diameter),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(\n metadata.first_layer_height || metadata.initial_layer_height || metadata.initial_layer_print_height,\n ),\n bedTemperature: this.parseFloat(\n metadata.bed_temperature_actual ||\n metadata.bed_temperature ||\n metadata.bed_temp ||\n metadata.bed_temperature_initial_layer,\n ),\n nozzleTemperature: this.parseFloat(\n metadata.nozzle_temperature || metadata.nozzle_temp || metadata.nozzle_temperature_initial_layer,\n ),\n fillDensity: metadata.sparse_infill_density || metadata.infill_density || metadata.fill_density || null,\n filamentType: metadata.filament_type || null,\n printerModel: metadata.printer_model || null,\n objects: [],\n thumbnails:\n plateThumbnails.length > 0\n ? plateThumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n };\n\n plates.push(plateMetadata);\n }\n\n return plates.sort((a, b) => a.plateNumber - b.plateNumber);\n }\n\n private parseGCodeHeader(gcode: string): Record<string, string> {\n const metadata: Record<string, string> = {};\n const lines = gcode.split(\"\\n\").slice(0, 1000); // Extended to include CONFIG_BLOCK and early G-code\n\n for (const line of lines) {\n // Parse comment lines\n if (line.startsWith(\";\")) {\n // Skip block markers\n if (line.includes(\"_BLOCK_START\") || line.includes(\"_BLOCK_END\")) continue;\n\n // Special case: BambuStudio version (no key:value pattern)\n // ; BambuStudio 02.04.00.70\n const bambuMatch = line.match(/;\\s*BambuStudio\\s+([\\d.]+)/i);\n if (bambuMatch) {\n metadata.bambustudio = `BambuStudio ${bambuMatch[1]}`;\n continue;\n }\n\n // Match patterns like:\n // ; key: value\n // ; key = value\n // ; key : value\n const match = line.match(/;\\s*([^=:]+?)\\s*[:=]\\s*(.+)/);\n if (match) {\n let key = match[1].trim().toLowerCase().replace(/\\s+/g, \"_\");\n let value = match[2].trim();\n\n // Remove bracketed units from key: \"total_filament_weight_[g]\" -> \"total_filament_weight_g\"\n // Also normalize special chars: [cm^3] -> cm3\n key = key.replace(/\\[([^\\]]+)\\]/g, (_, unit) => unit.replace(/\\^/g, \"\"));\n\n // Split value on semicolon and take first part (handles \"11m 15s; total estimated time: 18m 15s\")\n value = value.split(\";\")[0].trim();\n\n // Remove remaining bracketed units and percentages from value\n value = value.replace(/\\s*\\[.*?\\]\\s*/g, \"\"); // Remove [units]\n\n // Store the value (don't overwrite if already set from header)\n if (!metadata[key]) {\n metadata[key] = value;\n }\n }\n } else {\n // Parse actual G-code commands for temperatures\n // M140 S65 - Set bed temperature\n // M190 S65 - Wait for bed temperature\n if (!metadata.bed_temperature_actual) {\n const bedTempMatch = line.match(/^M1(40|90)\\s+S(\\d+)/);\n if (bedTempMatch && Number.parseInt(bedTempMatch[2]) > 0) {\n metadata.bed_temperature_actual = bedTempMatch[2];\n }\n }\n }\n }\n\n return metadata;\n }\n\n private extractThumbnails(zipEntries: AdmZip.IZipEntry[]): ParsedThumbnail[] {\n const thumbnails: ParsedThumbnail[] = [];\n\n const thumbEntries = zipEntries.filter(\n (e) => e.entryName.match(/Metadata\\/.*\\.(png|jpg|jpeg)/i) || e.entryName.match(/Thumbnails\\/.*/i),\n );\n\n for (const entry of thumbEntries) {\n const format = entry.entryName.match(/\\.(png|jpg|jpeg)$/i)?.[1].toUpperCase() || \"PNG\";\n const imageData = entry.getData();\n\n const sizeMatch = entry.entryName.match(/(\\d+)x(\\d+)/);\n let width = sizeMatch ? Number.parseInt(sizeMatch[1]) : 0;\n let height = sizeMatch ? Number.parseInt(sizeMatch[2]) : 0;\n\n if (width === 0 || height === 0) {\n const dimensions = getImageDimensions(imageData, format);\n width = dimensions.width;\n height = dimensions.height;\n }\n\n const base64Data = imageData.toString(\"base64\");\n\n thumbnails.push({\n width,\n height,\n format,\n data: base64Data,\n });\n }\n\n return thumbnails;\n }\n\n private parseFloat(value: string | undefined): number | null {\n if (!value) return null;\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseInt(value: string | undefined): number | null {\n if (!value) return null;\n const num = Number.parseInt(value, 10);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n // Try parsing as a duration string first (e.g., \"11m 15s\" or \"1h 30m\")\n const match = value.match(/(?:(\\d+)h)?(?:\\s*(\\d+)m)?(?:\\s*(\\d+)s)?/);\n if (match && (match[1] || match[2] || match[3])) {\n const hours = Number.parseInt(match[1] || \"0\");\n const minutes = Number.parseInt(match[2] || \"0\");\n const secs = Number.parseInt(match[3] || \"0\");\n return hours * 3600 + minutes * 60 + secs;\n }\n\n // Fallback to parsing as plain seconds\n const seconds = Number.parseFloat(value);\n if (!Number.isNaN(seconds)) return seconds;\n\n return null;\n }\n}\n"],"mappings":";;;;;;;;;AAoBA,IAAa,gBAAb,MAA2B;CACzB,MAAM,MAAM,UAA+C;EACzD,MAAM,QAAQ,MAAMA,KAAG,KAAK,SAAS;EACrC,MAAM,WAAWC,OAAK,SAAS,SAAS;EAGxC,MAAM,aAAa,IADH,OAAO,SACD,CAAC,YAAY;EAGnC,MAAM,oBAAoB,WAAW,MAAM,MAAM,EAAE,cAAc,gBAAgB;EACjF,IAAI,WAAgC,EAAE;EAEtC,IAAI,mBAAmB;GAErB,MAAM,cAAc,kBAAkB,SAAS,CAAC,SAAS,OAAO;GAChE,MAAM,WAAW,KAAK,MAAM,YAAY;GACxC,WAAW,KAAK,sBAAsB,SAAS;SAC1C;GAEL,MAAM,aAAa,WAAW,MAC3B,MAAM,EAAE,cAAc,sBAAsB,EAAE,cAAc,iCAC9D;GACD,WAAW,aAAa,KAAK,uBAAuB,WAAW,SAAS,CAAC,SAAS,OAAO,CAAC,GAAG,EAAE;;EAIjG,MAAM,SAAS,KAAK,cAAc,WAAW;EAC7C,MAAM,eAAe,OAAO,SAAS;EAGrC,MAAM,aAAa,KAAK,kBAAkB,WAAW;EAGrD,IAAI,oBAAoB,KAAK,UAAU,SAAS,UAAU;EAC1D,IAAI,yBAAyB,KAAK,WAAW,SAAS,uBAAuB,SAAS,eAAe;EACrG,IAAI,iBAAiB,KAAK,SAAS,SAAS,WAAW;EACvD,IAAI,yBAAyB,KAAK,WAAW,SAAS,aAAa;EACnE,IAAI,0BAA0B,KAAK,WAAW,SAAS,eAAe;EACtE,IAAI,0BAA0B,KAAK,WAAW,SAAS,gBAAgB;EACvE,IAAI,eAAe,KAAK,WAAW,SAAS,KAAK;EACjD,IAAI,wBAAwB,SAAS,iBAAiB,SAAS;EAG/D,IAAI,yBAAyB,KAAK,WAAW,SAAS,eAAe;EACrE,IAAI,sBAAsB,KAAK,WAAW,SAAS,eAAe,SAAS,aAAa;EACxF,IAAI,2BAA2B,KAAK,WAAW,SAAS,oBAAoB,SAAS,mBAAmB;EACxG,IAAI,kBAAkB,KAAK,WAAW,SAAS,WAAW,SAAS,gBAAgB;EACnF,IAAI,qBAAqB,KAAK,WAAW,SAAS,cAAc,SAAS,mBAAmB;EAC5F,IAAI,sBAAsB,SAAS,iBAAiB,SAAS,kBAAkB,SAAS,gBAAgB;EACxG,IAAI,uBAAuB,SAAS,gBAAgB,SAAS,iBAAiB;EAC9E,IAAI,uBAAuB,SAAS,gBAAgB,SAAS,iBAAiB;EAC9E,IAAI,2BAA2B,KAAK,WAAW,SAAS,oBAAoB,SAAS,kBAAkB,IAAI;EAE3G,IAAI,OAAO,UAAU,KAAK,OAAO,IAC/B,IAAI,OAAO,WAAW,GAAG;GAEvB,MAAM,QAAQ,OAAO;GACrB,oBAAoB,MAAM,yBAAyB;GACnD,yBAAyB,MAAM,qBAAqB;GACpD,iBAAiB,MAAM,eAAe;GACtC,yBAAyB,MAAM,kBAAkB;GACjD,0BAA0B,MAAM,mBAAmB;GACnD,0BAA0B,MAAM,2BAA2B;GAC3D,eAAe,MAAM,aAAa;GAClC,wBAAwB,MAAM,iBAAiB;GAC/C,yBAAyB,MAAM,oBAAoB;GACnD,sBAAsB,MAAM,eAAe;GAC3C,2BAA2B,MAAM,oBAAoB;GACrD,kBAAkB,MAAM,kBAAkB;GAC1C,qBAAqB,MAAM,qBAAqB;GAChD,sBAAsB,MAAM,eAAe;GAC3C,uBAAuB,MAAM,gBAAgB;GAC7C,uBAAuB,MAAM,gBAAgB;GAC7C,2BAA2B,MAAM,sBAAsB;SAClD;GAEL,oBAAoB,OAAO,QAAQ,KAAK,MAAW,OAAO,EAAE,yBAAyB,IAAI,EAAE;GAC3F,yBAAyB,OAAO,QAAQ,KAAK,MAAW,OAAO,EAAE,qBAAqB,IAAI,EAAE;GAC5F,yBAAyB,OAAO,QAAQ,KAAK,MAAW,OAAO,EAAE,kBAAkB,IAAI,EAAE;GACzF,0BAA0B,OAAO,QAAQ,KAAK,MAAW,OAAO,EAAE,mBAAmB,IAAI,EAAE;GAC3F,iBAAiB,KAAK,IAAI,GAAG,OAAO,KAAK,MAAW,EAAE,eAAe,EAAE,CAAC;GACxE,eAAe,KAAK,IAAI,GAAG,OAAO,KAAK,MAAW,EAAE,aAAa,EAAE,CAAC;GAGpE,MAAM,aAAa,OAAO;GAC1B,0BAA0B,WAAW,2BAA2B;GAChE,wBAAwB,WAAW,iBAAiB;GACpD,yBAAyB,WAAW,oBAAoB;GACxD,sBAAsB,WAAW,eAAe;GAChD,2BAA2B,WAAW,oBAAoB;GAC1D,kBAAkB,WAAW,kBAAkB;GAC/C,qBAAqB,WAAW,qBAAqB;GACrD,sBAAsB,WAAW,eAAe;GAChD,uBAAuB,WAAW,gBAAgB;GAClD,uBAAuB,WAAW,gBAAgB;GAClD,2BAA2B,WAAW,sBAAsB;;EAIhE,MAAM,aAA8B;GAClC;GACA,YAAY;GACZ,UAAU,MAAM;GAChB;GACA,aAAa,OAAO,UAAU;GAC9B,uBAAuB;GACvB,kBAAkB;GAClB,oBAAoB;GACpB,yBAAyB;GACzB,gBAAgB;GAChB,iBAAiB;GACjB,mBAAmB;GACnB,wBAAwB;GACxB,aAAa;GACb,kBAAkB;GAClB,gBAAgB;GAChB,mBAAmB;GACnB,aAAa;GACb,cAAc;GACd,cAAc;GACd,eAAe;GACf,WAAW;GACX,aAAa;GACb,YACE,WAAW,SAAS,IAChB,WAAW,KAAK,OAAO;IACrB,OAAO,EAAE;IACT,QAAQ,EAAE;IACV,QAAQ,EAAE;IACV,YAAY,EAAE,MAAM,UAAU;IAC/B,EAAE,GACH,KAAA;GACN,QAAQ,OAAO,SAAS,IAAI,SAAS,KAAA;GACtC;EAED,OAAO;GACL,KAAK;IACH,aAAa;IACb,QAAQ,OAAO,SAAS,IAAI,SAAS,KAAA;IACtC;GACD;GACA,QAAQ,OAAO,SAAS,IAAI,SAAS,KAAA;GACtC;;CAGH,sBAA8B,UAAoC;EAEhE,MAAM,WAAgC,EAAE;EAGxC,IAAI,SAAS,mBAAmB,KAAA,GAAW,SAAS,iBAAiB,OAAO,SAAS,eAAe;EACpG,IAAI,SAAS,0BAA0B,KAAA,GAAW,SAAS,YAAY,OAAO,SAAS,sBAAsB;EAC7G,IAAI,SAAS,qBAAqB,KAAA,GAAW,SAAS,mBAAmB,OAAO,SAAS,iBAAiB;EAC1G,IAAI,SAAS,oBAAoB,KAAA,GAAW,SAAS,kBAAkB,OAAO,SAAS,gBAAgB;EACvG,IAAI,SAAS,sBAAsB,KAAA,GAAW,SAAS,iBAAiB,OAAO,SAAS,kBAAkB;EAC1G,IAAI,SAAS,gBAAgB,KAAA,GAAW,SAAS,cAAc,OAAO,SAAS,YAAY;EAC3F,IAAI,SAAS,qBAAqB,KAAA,GAAW,SAAS,mBAAmB,OAAO,SAAS,iBAAiB;EAC1G,IAAI,SAAS,YAAY,KAAA,GAAW,SAAS,UAAU,OAAO,SAAS,QAAQ;EAC/E,IAAI,SAAS,eAAe,KAAA,GAAW,SAAS,aAAa,OAAO,SAAS,WAAW;EACxF,IAAI,SAAS,gBAAgB,KAAA,GAAW,SAAS,gBAAgB,OAAO,SAAS,YAAY;EAC7F,IAAI,SAAS,iBAAiB,KAAA,GAAW,SAAS,eAAe,SAAS;EAC1E,IAAI,SAAS,iBAAiB,KAAA,GAAW,SAAS,eAAe,SAAS;EAE1E,OAAO;;CAGT,uBAA+B,KAAqC;EAClE,MAAM,WAAmC,EAAE;EAa3C,KAAK,MAAM,WAAW;GATpB;GACA;GACA;GACA;GACA;GACA;GACA;GAG4B,EAAE;GAC9B,MAAM,QAAQ,IAAI,MAAM,QAAQ;GAChC,IAAI,OAAO;IACT,MAAM,MAAM,QAAQ,OAAO,MAAM,YAAY,GAAG,MAAM;IACtD,SAAS,OAAO,MAAM;;;EAK1B,MAAM,iBAAiB,IAAI,MAAM,sBAAsB;EACvD,IAAI,gBAAgB;GAClB,SAAS,YAAY,eAAe;GACpC,SAAS,gBAAgB,eAAe;;EAG1C,OAAO;;CAGT,cAAsB,YAAwE;EAC5F,MAAM,SAAiD,EAAE;EAGzD,MAAM,eAAe,WAAW,QAC7B,MAAM,EAAE,UAAU,MAAM,8BAA8B,IAAI,CAAC,EAAE,UAAU,SAAS,OAAO,CACzF;EAED,IAAI,aAAa,WAAW,GAE1B,OAAO,EAAE;EAGX,KAAK,MAAM,SAAS,cAAc;GAChC,MAAM,aAAa,MAAM,UAAU,MAAM,qBAAqB;GAC9D,IAAI,CAAC,YAAY;GAGjB,MAAM,cAAc,OAAO,SAAS,WAAW,GAAG;GAElD,MAAM,eAAe,MAAM,SAAS,CAAC,SAAS,QAAQ,GAAG,KAAK,IAAI,KAAO,MAAM,SAAS,CAAC,OAAO,CAAC;GAGjG,MAAM,WAAW,KAAK,iBAAiB,aAAa;GASpD,MAAM,kBANc,WAAW,QAC5B,MACC,EAAE,UAAU,SAAS,SAAS,WAAW,KAAK,KAC7C,EAAE,UAAU,SAAS,OAAO,IAAI,EAAE,UAAU,SAAS,OAAO,EAGX,CAAC,KAAK,MAAM;IAChE,MAAM,SAAS,EAAE,UAAU,SAAS,OAAO,GAAG,QAAQ;IACtD,MAAM,YAAY,EAAE,SAAS;IAE7B,MAAM,YAAY,EAAE,UAAU,MAAM,cAAc;IAClD,IAAI,QAAQ,YAAY,OAAO,SAAS,UAAU,GAAG,GAAG;IACxD,IAAI,SAAS,YAAY,OAAO,SAAS,UAAU,GAAG,GAAG;IAEzD,IAAI,UAAU,KAAK,WAAW,GAAG;KAC/B,MAAM,aAAa,mBAAmB,WAAW,OAAO;KACxD,QAAQ,WAAW;KACnB,SAAS,WAAW;;IAGtB,OAAO;KACL;KACA;KACA;KACA,MAAM,UAAU,SAAS,SAAS;KACnC;KACD;GAUF,MAAM,gBAAgB;IACpB;IACA,uBAXgB,KAAK,UACrB,SAAS,uBAAuB,SAAS,wBAAwB,SAAS,WAU1C;IAChC,mBATqB,KAAK,WAC1B,SAAS,2BAA2B,SAAS,mBAAmB,SAAS,sBAQxC;IACjC,aAPiB,KAAK,SAAS,SAAS,sBAAsB,SAAS,eAAe,SAAS,aAOxE;IACvB,gBAAgB,KAAK,WACnB,SAAS,4BAA4B,SAAS,sBAAsB,SAAS,iBAC9E;IACD,iBAAiB,KAAK,WAAW,SAAS,6BAA6B,SAAS,oBAAoB;IACpG,yBAAyB,KAAK,WAAW,SAAS,iBAAiB;IACnE,oBAAoB,KAAK,WAAW,SAAS,kBAAkB,IAAI;IACnE,WAAW,KAAK,WAAW,SAAS,gBAAgB,SAAS,YAAY;IACzE,eAAe,SAAS,eAAe,SAAS,kBAAkB,SAAS,UAAU;IACrF,kBAAkB,KAAK,WAAW,SAAS,gBAAgB;IAC3D,aAAa,KAAK,WAAW,SAAS,aAAa;IACnD,kBAAkB,KAAK,WACrB,SAAS,sBAAsB,SAAS,wBAAwB,SAAS,2BAC1E;IACD,gBAAgB,KAAK,WACnB,SAAS,0BACP,SAAS,mBACT,SAAS,YACT,SAAS,8BACZ;IACD,mBAAmB,KAAK,WACtB,SAAS,sBAAsB,SAAS,eAAe,SAAS,iCACjE;IACD,aAAa,SAAS,yBAAyB,SAAS,kBAAkB,SAAS,gBAAgB;IACnG,cAAc,SAAS,iBAAiB;IACxC,cAAc,SAAS,iBAAiB;IACxC,SAAS,EAAE;IACX,YACE,gBAAgB,SAAS,IACrB,gBAAgB,KAAK,OAAO;KAC1B,OAAO,EAAE;KACT,QAAQ,EAAE;KACV,QAAQ,EAAE;KACV,YAAY,EAAE,MAAM,UAAU;KAC/B,EAAE,GACH,KAAA;IACP;GAED,OAAO,KAAK,cAAc;;EAG5B,OAAO,OAAO,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,YAAY;;CAG7D,iBAAyB,OAAuC;EAC9D,MAAM,WAAmC,EAAE;EAC3C,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC,MAAM,GAAG,IAAK;EAE9C,KAAK,MAAM,QAAQ,OAEjB,IAAI,KAAK,WAAW,IAAI,EAAE;GAExB,IAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,aAAa,EAAE;GAIlE,MAAM,aAAa,KAAK,MAAM,8BAA8B;GAC5D,IAAI,YAAY;IACd,SAAS,cAAc,eAAe,WAAW;IACjD;;GAOF,MAAM,QAAQ,KAAK,MAAM,8BAA8B;GACvD,IAAI,OAAO;IACT,IAAI,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,QAAQ,IAAI;IAC5D,IAAI,QAAQ,MAAM,GAAG,MAAM;IAI3B,MAAM,IAAI,QAAQ,kBAAkB,GAAG,SAAS,KAAK,QAAQ,OAAO,GAAG,CAAC;IAGxE,QAAQ,MAAM,MAAM,IAAI,CAAC,GAAG,MAAM;IAGlC,QAAQ,MAAM,QAAQ,kBAAkB,GAAG;IAG3C,IAAI,CAAC,SAAS,MACZ,SAAS,OAAO;;SAOpB,IAAI,CAAC,SAAS,wBAAwB;GACpC,MAAM,eAAe,KAAK,MAAM,sBAAsB;GACtD,IAAI,gBAAgB,OAAO,SAAS,aAAa,GAAG,GAAG,GACrD,SAAS,yBAAyB,aAAa;;EAMvD,OAAO;;CAGT,kBAA0B,YAAmD;EAC3E,MAAM,aAAgC,EAAE;EAExC,MAAM,eAAe,WAAW,QAC7B,MAAM,EAAE,UAAU,MAAM,gCAAgC,IAAI,EAAE,UAAU,MAAM,kBAAkB,CAClG;EAED,KAAK,MAAM,SAAS,cAAc;GAChC,MAAM,SAAS,MAAM,UAAU,MAAM,qBAAqB,GAAG,GAAG,aAAa,IAAI;GACjF,MAAM,YAAY,MAAM,SAAS;GAEjC,MAAM,YAAY,MAAM,UAAU,MAAM,cAAc;GACtD,IAAI,QAAQ,YAAY,OAAO,SAAS,UAAU,GAAG,GAAG;GACxD,IAAI,SAAS,YAAY,OAAO,SAAS,UAAU,GAAG,GAAG;GAEzD,IAAI,UAAU,KAAK,WAAW,GAAG;IAC/B,MAAM,aAAa,mBAAmB,WAAW,OAAO;IACxD,QAAQ,WAAW;IACnB,SAAS,WAAW;;GAGtB,MAAM,aAAa,UAAU,SAAS,SAAS;GAE/C,WAAW,KAAK;IACd;IACA;IACA;IACA,MAAM;IACP,CAAC;;EAGJ,OAAO;;CAGT,WAAmB,OAA0C;EAC3D,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,MAAM,OAAO,WAAW,MAAM;EACpC,OAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;CAGpC,SAAiB,OAA0C;EACzD,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,MAAM,OAAO,SAAS,OAAO,GAAG;EACtC,OAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;CAGpC,UAAkB,OAA0C;EAC1D,IAAI,CAAC,OAAO,OAAO;EAGnB,MAAM,QAAQ,MAAM,MAAM,0CAA0C;EACpE,IAAI,UAAU,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK;GAC/C,MAAM,QAAQ,OAAO,SAAS,MAAM,MAAM,IAAI;GAC9C,MAAM,UAAU,OAAO,SAAS,MAAM,MAAM,IAAI;GAChD,MAAM,OAAO,OAAO,SAAS,MAAM,MAAM,IAAI;GAC7C,OAAO,QAAQ,OAAO,UAAU,KAAK;;EAIvC,MAAM,UAAU,OAAO,WAAW,MAAM;EACxC,IAAI,CAAC,OAAO,MAAM,QAAQ,EAAE,OAAO;EAEnC,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"bgcode.parser.js","names":[],"sources":["../../../src/utils/parsers/bgcode.parser.ts"],"sourcesContent":["import path from \"node:path\";\nimport { BGCodeMetadata } from \"@/entities/print-job.entity\";\nimport fs, { open } from \"node:fs/promises\";\nimport {\n parseFileHeader,\n parseBlockHeaders,\n getBlockData,\n decompressBlock,\n extractMetadataFromBlocks,\n} from \"../bgcode/bgcode.utils\";\nimport {\n BgCodeBlockTypes,\n type BgCodeBlockHeader,\n type BgCodeThumbnailParameters,\n BgCodeBlockTypeName,\n BgCodeCompressionName,\n} from \"../bgcode/bgcode.types\";\nimport { processThumbnail } from \"../bgcode/bgcode-thumbnail.parser\";\nimport { ParsedThumbnail } from \"./parser.types\";\n\ninterface BGCodeParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n blocks?: Array<{\n type: string;\n compressedSize: number;\n uncompressedSize: number;\n compression: string;\n }>;\n };\n normalized: BGCodeMetadata;\n}\n\n/**\n * BGCode parser for extracting metadata from .bgcode files\n * BGCode is a binary G-code format used by Prusa printers\n */\nexport class BGCodeParser {\n async parse(filePath: string): Promise<BGCodeParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = path.basename(filePath);\n\n const fileHandle = await open(filePath, \"r\");\n\n try {\n const { version, checksumType } = await parseFileHeader(fileHandle);\n if (version !== 1) {\n throw new Error(`Unsupported BGCode version: ${version}`);\n }\n\n const blockHeaders = await parseBlockHeaders(fileHandle, stats.size, checksumType, true);\n\n const metadata = await extractMetadataFromBlocks(fileHandle, blockHeaders);\n const thumbnails = await this.extractThumbnailsFromBlocks(fileHandle, blockHeaders);\n\n const isMmu =\n this.isMmuData(metadata.nozzle_diameter) ||\n this.isMmuData(metadata.temperature) ||\n this.isMmuData(metadata.filament_used_mm) ||\n this.isMmuData(metadata.bed_temperature) ||\n this.isMmuData(metadata.filament_type, \";\");\n\n const normalized: BGCodeMetadata = {\n fileName,\n fileFormat: \"bgcode\",\n fileSize: stats.size,\n producer: metadata.producer,\n producedOn: metadata.produced_on,\n checksumType: checksumType === 1 ? \"CRC32\" : \"None\",\n isMmu: isMmu || undefined,\n gcodePrintTimeSeconds: this.parseTime(metadata.estimated_printing_time_normal_mode || metadata.print_time),\n gcodePrintTimeSecondsSilent: this.parseTime(metadata.estimated_printing_time_silent_mode),\n nozzleDiameterMm: isMmu\n ? this.parseNumberArray(metadata.nozzle_diameter)\n : this.parseFirstValue(metadata.nozzle_diameter),\n filamentDiameterMm: isMmu\n ? this.parseNumberArray(metadata.filament_diameter)\n : this.parseFirstValue(metadata.filament_diameter) || 1.75,\n filamentDensityGramsCm3: isMmu\n ? this.parseNumberArray(metadata.filament_density)\n : this.parseFirstValue(metadata.filament_density),\n filamentUsedMm: isMmu\n ? this.parseNumberArray(metadata.filament_used_mm)\n : this.parseFirstValue(metadata.filament_used_mm),\n filamentUsedCm3: isMmu\n ? this.parseNumberArray(metadata.filament_used_cm3)\n : this.parseFirstValue(metadata.filament_used_cm3),\n filamentUsedGrams: isMmu\n ? this.parseNumberArray(metadata.filament_used_g)\n : this.parseFirstValue(metadata.filament_used_g),\n totalFilamentUsedGrams: isMmu\n ? this.sumNumberArray(this.parseNumberArray(metadata.filament_used_g))\n : this.parseFirstValue(metadata.filament_used_g),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height),\n bedTemperature: isMmu\n ? this.parseNumberArray(metadata.bed_temperature)\n : this.parseFirstValue(metadata.bed_temperature),\n nozzleTemperature: isMmu\n ? this.parseNumberArray(metadata.temperature)\n : this.parseFirstValue(metadata.temperature),\n fillDensity: metadata.fill_density || null,\n filamentType: isMmu\n ? this.parseStringArray(metadata.filament_type, \";\")\n : this.parseFirstCsvValue(metadata.filament_type),\n printerModel: metadata.printer_model || null,\n slicerVersion: metadata.producer || null,\n maxLayerZ: this.parseFloat(metadata.max_layer_z),\n totalLayers: this.parseInt(metadata.total_layers || metadata.layer_count),\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n blocks: blockHeaders.map((b) => ({\n type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`,\n compressedSize: b.compressedSize,\n uncompressedSize: b.uncompressedSize,\n compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})`,\n })),\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n blocks: blockHeaders.map((b) => ({\n type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`,\n compressedSize: b.compressedSize,\n uncompressedSize: b.uncompressedSize,\n compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})`,\n })),\n },\n normalized,\n };\n } finally {\n await fileHandle.close();\n }\n }\n\n private async extractThumbnailsFromBlocks(\n fileHandle: any,\n blockHeaders: BgCodeBlockHeader[],\n ): Promise<ParsedThumbnail[]> {\n const thumbnails: ParsedThumbnail[] = [];\n\n const thumbnailBlocks = blockHeaders.filter((b) => b.type === BgCodeBlockTypes.Thumbnail);\n\n for (const header of thumbnailBlocks) {\n const parameters = header.parameters as BgCodeThumbnailParameters;\n\n const blockData = await getBlockData(fileHandle, header);\n const imageData = decompressBlock(header.compression, blockData);\n\n const processed = processThumbnail(imageData, parameters);\n\n thumbnails.push({\n width: parameters.width,\n height: parameters.height,\n format: processed.extension,\n data: processed.data.toString(\"base64\"),\n });\n }\n\n return thumbnails;\n }\n\n private parseNumber(value: string | undefined, parser: (val: string) => number): number | null {\n if (!value) return null;\n const num = parser(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFloat(value: string | undefined): number | null {\n return this.parseNumber(value, Number.parseFloat);\n }\n\n private parseInt(value: string | undefined): number | null {\n return this.parseNumber(value, (val) => Number.parseInt(val, 10));\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n let totalSeconds = 0;\n const hours = new RegExp(/(\\d+)h/).exec(value);\n const minutes = new RegExp(/(\\d+)m/).exec(value);\n const seconds = new RegExp(/(\\d+)s/).exec(value);\n\n if (hours) totalSeconds += Number.parseInt(hours[1]) * 3600;\n if (minutes) totalSeconds += Number.parseInt(minutes[1]) * 60;\n if (seconds) totalSeconds += Number.parseInt(seconds[1]);\n\n if (hours || minutes || seconds) return totalSeconds;\n\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFirstValue(value: string | undefined): number | null {\n if (!value) return null;\n\n const firstValue = value.split(\",\")[0].trim();\n const num = Number.parseFloat(firstValue);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFirstCsvValue(value: string | undefined): string | null {\n if (!value) return null;\n return value.trim();\n }\n\n private parseNumberArray(value: string | undefined): number[] | null {\n if (!value) return null;\n const values = value\n .split(\",\")\n .map((v) => Number.parseFloat(v.trim()))\n .filter((n) => !Number.isNaN(n));\n return values.length > 0 ? values : null;\n }\n\n private parseStringArray(value: string | undefined, separator: string = \";\"): string[] | null {\n if (!value) return null;\n const values = value\n .split(separator)\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n return values.length > 0 ? values : null;\n }\n\n private isMmuData(value: string | undefined, separator: string = \",\"): boolean {\n if (!value) return false;\n const parts = value\n .split(separator)\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n return parts.length > 1;\n }\n\n private sumNumberArray(values: number[] | null): number | null {\n if (!values || values.length === 0) return null;\n return values.reduce((sum, val) => sum + val, 0);\n }\n}\n"],"mappings":";;;;;;;;;;AAqCA,IAAa,eAAb,MAA0B;CACxB,MAAM,MAAM,UAA8C;EACxD,MAAM,QAAQ,MAAM,GAAG,KAAK,SAAS;EACrC,MAAM,WAAW,KAAK,SAAS,SAAS;EAExC,MAAM,aAAa,MAAM,KAAK,UAAU,IAAI;AAE5C,MAAI;GACF,MAAM,EAAE,SAAS,iBAAiB,MAAM,gBAAgB,WAAW;AACnE,OAAI,YAAY,EACd,OAAM,IAAI,MAAM,+BAA+B,UAAU;GAG3D,MAAM,eAAe,MAAM,kBAAkB,YAAY,MAAM,MAAM,cAAc,KAAK;GAExF,MAAM,WAAW,MAAM,0BAA0B,YAAY,aAAa;GAC1E,MAAM,aAAa,MAAM,KAAK,4BAA4B,YAAY,aAAa;GAEnF,MAAM,QACJ,KAAK,UAAU,SAAS,gBAAgB,IACxC,KAAK,UAAU,SAAS,YAAY,IACpC,KAAK,UAAU,SAAS,iBAAiB,IACzC,KAAK,UAAU,SAAS,gBAAgB,IACxC,KAAK,UAAU,SAAS,eAAe,IAAI;GAE7C,MAAM,aAA6B;IACjC;IACA,YAAY;IACZ,UAAU,MAAM;IAChB,UAAU,SAAS;IACnB,YAAY,SAAS;IACrB,cAAc,iBAAiB,IAAI,UAAU;IAC7C,OAAO,SAAS,KAAA;IAChB,uBAAuB,KAAK,UAAU,SAAS,uCAAuC,SAAS,WAAW;IAC1G,6BAA6B,KAAK,UAAU,SAAS,oCAAoC;IACzF,kBAAkB,QACd,KAAK,iBAAiB,SAAS,gBAAgB,GAC/C,KAAK,gBAAgB,SAAS,gBAAgB;IAClD,oBAAoB,QAChB,KAAK,iBAAiB,SAAS,kBAAkB,GACjD,KAAK,gBAAgB,SAAS,kBAAkB,IAAI;IACxD,yBAAyB,QACrB,KAAK,iBAAiB,SAAS,iBAAiB,GAChD,KAAK,gBAAgB,SAAS,iBAAiB;IACnD,gBAAgB,QACZ,KAAK,iBAAiB,SAAS,iBAAiB,GAChD,KAAK,gBAAgB,SAAS,iBAAiB;IACnD,iBAAiB,QACb,KAAK,iBAAiB,SAAS,kBAAkB,GACjD,KAAK,gBAAgB,SAAS,kBAAkB;IACpD,mBAAmB,QACf,KAAK,iBAAiB,SAAS,gBAAgB,GAC/C,KAAK,gBAAgB,SAAS,gBAAgB;IAClD,wBAAwB,QACpB,KAAK,eAAe,KAAK,iBAAiB,SAAS,gBAAgB,CAAC,GACpE,KAAK,gBAAgB,SAAS,gBAAgB;IAClD,aAAa,KAAK,WAAW,SAAS,aAAa;IACnD,kBAAkB,KAAK,WAAW,SAAS,sBAAsB,SAAS,qBAAqB;IAC/F,gBAAgB,QACZ,KAAK,iBAAiB,SAAS,gBAAgB,GAC/C,KAAK,gBAAgB,SAAS,gBAAgB;IAClD,mBAAmB,QACf,KAAK,iBAAiB,SAAS,YAAY,GAC3C,KAAK,gBAAgB,SAAS,YAAY;IAC9C,aAAa,SAAS,gBAAgB;IACtC,cAAc,QACV,KAAK,iBAAiB,SAAS,eAAe,IAAI,GAClD,KAAK,mBAAmB,SAAS,cAAc;IACnD,cAAc,SAAS,iBAAiB;IACxC,eAAe,SAAS,YAAY;IACpC,WAAW,KAAK,WAAW,SAAS,YAAY;IAChD,aAAa,KAAK,SAAS,SAAS,gBAAgB,SAAS,YAAY;IACzE,YACE,WAAW,SAAS,IAChB,WAAW,KAAK,OAAO;KACrB,OAAO,EAAE;KACT,QAAQ,EAAE;KACV,QAAQ,EAAE;KACV,YAAY,EAAE,MAAM,UAAU;KAC/B,EAAE,GACH,KAAA;IACN,QAAQ,aAAa,KAAK,OAAO;KAC/B,MAAM,oBAAoB,EAAE,SAAS,WAAW,EAAE,KAAK;KACvD,gBAAgB,EAAE;KAClB,kBAAkB,EAAE;KACpB,aAAa,sBAAsB,EAAE,gBAAgB,WAAW,EAAE,YAAY;KAC/E,EAAE;IACJ;AAED,UAAO;IACL,KAAK;KACH,aAAa;KACb,QAAQ,aAAa,KAAK,OAAO;MAC/B,MAAM,oBAAoB,EAAE,SAAS,WAAW,EAAE,KAAK;MACvD,gBAAgB,EAAE;MAClB,kBAAkB,EAAE;MACpB,aAAa,sBAAsB,EAAE,gBAAgB,WAAW,EAAE,YAAY;MAC/E,EAAE;KACJ;IACD;IACD;YACO;AACR,SAAM,WAAW,OAAO;;;CAI5B,MAAc,4BACZ,YACA,cAC4B;EAC5B,MAAM,aAAgC,EAAE;EAExC,MAAM,kBAAkB,aAAa,QAAQ,MAAM,EAAE,SAAS,iBAAiB,UAAU;AAEzF,OAAK,MAAM,UAAU,iBAAiB;GACpC,MAAM,aAAa,OAAO;GAE1B,MAAM,YAAY,MAAM,aAAa,YAAY,OAAO;GAGxD,MAAM,YAAY,iBAFA,gBAAgB,OAAO,aAAa,UAEV,EAAE,WAAW;AAEzD,cAAW,KAAK;IACd,OAAO,WAAW;IAClB,QAAQ,WAAW;IACnB,QAAQ,UAAU;IAClB,MAAM,UAAU,KAAK,SAAS,SAAS;IACxC,CAAC;;AAGJ,SAAO;;CAGT,YAAoB,OAA2B,QAAgD;AAC7F,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,MAAM,OAAO,MAAM;AACzB,SAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;CAGpC,WAAmB,OAA0C;AAC3D,SAAO,KAAK,YAAY,OAAO,OAAO,WAAW;;CAGnD,SAAiB,OAA0C;AACzD,SAAO,KAAK,YAAY,QAAQ,QAAQ,OAAO,SAAS,KAAK,GAAG,CAAC;;CAGnE,UAAkB,OAA0C;AAC1D,MAAI,CAAC,MAAO,QAAO;EAEnB,IAAI,eAAe;EACnB,MAAM,yBAAQ,IAAI,OAAO,SAAS,EAAC,KAAK,MAAM;EAC9C,MAAM,2BAAU,IAAI,OAAO,SAAS,EAAC,KAAK,MAAM;EAChD,MAAM,2BAAU,IAAI,OAAO,SAAS,EAAC,KAAK,MAAM;AAEhD,MAAI,MAAO,iBAAgB,OAAO,SAAS,MAAM,GAAG,GAAG;AACvD,MAAI,QAAS,iBAAgB,OAAO,SAAS,QAAQ,GAAG,GAAG;AAC3D,MAAI,QAAS,iBAAgB,OAAO,SAAS,QAAQ,GAAG;AAExD,MAAI,SAAS,WAAW,QAAS,QAAO;EAExC,MAAM,MAAM,OAAO,WAAW,MAAM;AACpC,SAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;CAGpC,gBAAwB,OAA0C;AAChE,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,aAAa,MAAM,MAAM,IAAI,CAAC,GAAG,MAAM;EAC7C,MAAM,MAAM,OAAO,WAAW,WAAW;AACzC,SAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;CAGpC,mBAA2B,OAA0C;AACnE,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,MAAM;;CAGrB,iBAAyB,OAA4C;AACnE,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,SAAS,MACZ,MAAM,IAAI,CACV,KAAK,MAAM,OAAO,WAAW,EAAE,MAAM,CAAC,CAAC,CACvC,QAAQ,MAAM,CAAC,OAAO,MAAM,EAAE,CAAC;AAClC,SAAO,OAAO,SAAS,IAAI,SAAS;;CAGtC,iBAAyB,OAA2B,YAAoB,KAAsB;AAC5F,MAAI,CAAC,MAAO,QAAO;EACnB,MAAM,SAAS,MACZ,MAAM,UAAU,CAChB,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;AAC9B,SAAO,OAAO,SAAS,IAAI,SAAS;;CAGtC,UAAkB,OAA2B,YAAoB,KAAc;AAC7E,MAAI,CAAC,MAAO,QAAO;AAKnB,SAJc,MACX,MAAM,UAAU,CAChB,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAChB,CAAC,SAAS;;CAGxB,eAAuB,QAAwC;AAC7D,MAAI,CAAC,UAAU,OAAO,WAAW,EAAG,QAAO;AAC3C,SAAO,OAAO,QAAQ,KAAK,QAAQ,MAAM,KAAK,EAAE"}
1
+ {"version":3,"file":"bgcode.parser.js","names":[],"sources":["../../../src/utils/parsers/bgcode.parser.ts"],"sourcesContent":["import path from \"node:path\";\nimport { BGCodeMetadata } from \"@/entities/print-job.entity\";\nimport fs, { open } from \"node:fs/promises\";\nimport {\n parseFileHeader,\n parseBlockHeaders,\n getBlockData,\n decompressBlock,\n extractMetadataFromBlocks,\n} from \"../bgcode/bgcode.utils\";\nimport {\n BgCodeBlockTypes,\n type BgCodeBlockHeader,\n type BgCodeThumbnailParameters,\n BgCodeBlockTypeName,\n BgCodeCompressionName,\n} from \"../bgcode/bgcode.types\";\nimport { processThumbnail } from \"../bgcode/bgcode-thumbnail.parser\";\nimport { ParsedThumbnail } from \"./parser.types\";\n\ninterface BGCodeParseResult {\n raw: {\n _thumbnails?: ParsedThumbnail[];\n blocks?: Array<{\n type: string;\n compressedSize: number;\n uncompressedSize: number;\n compression: string;\n }>;\n };\n normalized: BGCodeMetadata;\n}\n\n/**\n * BGCode parser for extracting metadata from .bgcode files\n * BGCode is a binary G-code format used by Prusa printers\n */\nexport class BGCodeParser {\n async parse(filePath: string): Promise<BGCodeParseResult> {\n const stats = await fs.stat(filePath);\n const fileName = path.basename(filePath);\n\n const fileHandle = await open(filePath, \"r\");\n\n try {\n const { version, checksumType } = await parseFileHeader(fileHandle);\n if (version !== 1) {\n throw new Error(`Unsupported BGCode version: ${version}`);\n }\n\n const blockHeaders = await parseBlockHeaders(fileHandle, stats.size, checksumType, true);\n\n const metadata = await extractMetadataFromBlocks(fileHandle, blockHeaders);\n const thumbnails = await this.extractThumbnailsFromBlocks(fileHandle, blockHeaders);\n\n const isMmu =\n this.isMmuData(metadata.nozzle_diameter) ||\n this.isMmuData(metadata.temperature) ||\n this.isMmuData(metadata.filament_used_mm) ||\n this.isMmuData(metadata.bed_temperature) ||\n this.isMmuData(metadata.filament_type, \";\");\n\n const normalized: BGCodeMetadata = {\n fileName,\n fileFormat: \"bgcode\",\n fileSize: stats.size,\n producer: metadata.producer,\n producedOn: metadata.produced_on,\n checksumType: checksumType === 1 ? \"CRC32\" : \"None\",\n isMmu: isMmu || undefined,\n gcodePrintTimeSeconds: this.parseTime(metadata.estimated_printing_time_normal_mode || metadata.print_time),\n gcodePrintTimeSecondsSilent: this.parseTime(metadata.estimated_printing_time_silent_mode),\n nozzleDiameterMm: isMmu\n ? this.parseNumberArray(metadata.nozzle_diameter)\n : this.parseFirstValue(metadata.nozzle_diameter),\n filamentDiameterMm: isMmu\n ? this.parseNumberArray(metadata.filament_diameter)\n : this.parseFirstValue(metadata.filament_diameter) || 1.75,\n filamentDensityGramsCm3: isMmu\n ? this.parseNumberArray(metadata.filament_density)\n : this.parseFirstValue(metadata.filament_density),\n filamentUsedMm: isMmu\n ? this.parseNumberArray(metadata.filament_used_mm)\n : this.parseFirstValue(metadata.filament_used_mm),\n filamentUsedCm3: isMmu\n ? this.parseNumberArray(metadata.filament_used_cm3)\n : this.parseFirstValue(metadata.filament_used_cm3),\n filamentUsedGrams: isMmu\n ? this.parseNumberArray(metadata.filament_used_g)\n : this.parseFirstValue(metadata.filament_used_g),\n totalFilamentUsedGrams: isMmu\n ? this.sumNumberArray(this.parseNumberArray(metadata.filament_used_g))\n : this.parseFirstValue(metadata.filament_used_g),\n layerHeight: this.parseFloat(metadata.layer_height),\n firstLayerHeight: this.parseFloat(metadata.first_layer_height || metadata.initial_layer_height),\n bedTemperature: isMmu\n ? this.parseNumberArray(metadata.bed_temperature)\n : this.parseFirstValue(metadata.bed_temperature),\n nozzleTemperature: isMmu\n ? this.parseNumberArray(metadata.temperature)\n : this.parseFirstValue(metadata.temperature),\n fillDensity: metadata.fill_density || null,\n filamentType: isMmu\n ? this.parseStringArray(metadata.filament_type, \";\")\n : this.parseFirstCsvValue(metadata.filament_type),\n printerModel: metadata.printer_model || null,\n slicerVersion: metadata.producer || null,\n maxLayerZ: this.parseFloat(metadata.max_layer_z),\n totalLayers: this.parseInt(metadata.total_layers || metadata.layer_count),\n thumbnails:\n thumbnails.length > 0\n ? thumbnails.map((t) => ({\n width: t.width,\n height: t.height,\n format: t.format,\n dataLength: t.data?.length || 0,\n }))\n : undefined,\n blocks: blockHeaders.map((b) => ({\n type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`,\n compressedSize: b.compressedSize,\n uncompressedSize: b.uncompressedSize,\n compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})`,\n })),\n };\n\n return {\n raw: {\n _thumbnails: thumbnails,\n blocks: blockHeaders.map((b) => ({\n type: BgCodeBlockTypeName[b.type] || `Unknown(${b.type})`,\n compressedSize: b.compressedSize,\n uncompressedSize: b.uncompressedSize,\n compression: BgCodeCompressionName[b.compression] || `Unknown(${b.compression})`,\n })),\n },\n normalized,\n };\n } finally {\n await fileHandle.close();\n }\n }\n\n private async extractThumbnailsFromBlocks(\n fileHandle: any,\n blockHeaders: BgCodeBlockHeader[],\n ): Promise<ParsedThumbnail[]> {\n const thumbnails: ParsedThumbnail[] = [];\n\n const thumbnailBlocks = blockHeaders.filter((b) => b.type === BgCodeBlockTypes.Thumbnail);\n\n for (const header of thumbnailBlocks) {\n const parameters = header.parameters as BgCodeThumbnailParameters;\n\n const blockData = await getBlockData(fileHandle, header);\n const imageData = decompressBlock(header.compression, blockData);\n\n const processed = processThumbnail(imageData, parameters);\n\n thumbnails.push({\n width: parameters.width,\n height: parameters.height,\n format: processed.extension,\n data: processed.data.toString(\"base64\"),\n });\n }\n\n return thumbnails;\n }\n\n private parseNumber(value: string | undefined, parser: (val: string) => number): number | null {\n if (!value) return null;\n const num = parser(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFloat(value: string | undefined): number | null {\n return this.parseNumber(value, Number.parseFloat);\n }\n\n private parseInt(value: string | undefined): number | null {\n return this.parseNumber(value, (val) => Number.parseInt(val, 10));\n }\n\n private parseTime(value: string | undefined): number | null {\n if (!value) return null;\n\n let totalSeconds = 0;\n const hours = new RegExp(/(\\d+)h/).exec(value);\n const minutes = new RegExp(/(\\d+)m/).exec(value);\n const seconds = new RegExp(/(\\d+)s/).exec(value);\n\n if (hours) totalSeconds += Number.parseInt(hours[1]) * 3600;\n if (minutes) totalSeconds += Number.parseInt(minutes[1]) * 60;\n if (seconds) totalSeconds += Number.parseInt(seconds[1]);\n\n if (hours || minutes || seconds) return totalSeconds;\n\n const num = Number.parseFloat(value);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFirstValue(value: string | undefined): number | null {\n if (!value) return null;\n\n const firstValue = value.split(\",\")[0].trim();\n const num = Number.parseFloat(firstValue);\n return Number.isNaN(num) ? null : num;\n }\n\n private parseFirstCsvValue(value: string | undefined): string | null {\n if (!value) return null;\n return value.trim();\n }\n\n private parseNumberArray(value: string | undefined): number[] | null {\n if (!value) return null;\n const values = value\n .split(\",\")\n .map((v) => Number.parseFloat(v.trim()))\n .filter((n) => !Number.isNaN(n));\n return values.length > 0 ? values : null;\n }\n\n private parseStringArray(value: string | undefined, separator: string = \";\"): string[] | null {\n if (!value) return null;\n const values = value\n .split(separator)\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n return values.length > 0 ? values : null;\n }\n\n private isMmuData(value: string | undefined, separator: string = \",\"): boolean {\n if (!value) return false;\n const parts = value\n .split(separator)\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n return parts.length > 1;\n }\n\n private sumNumberArray(values: number[] | null): number | null {\n if (!values || values.length === 0) return null;\n return values.reduce((sum, val) => sum + val, 0);\n }\n}\n"],"mappings":"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"}