FileConverter.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import metrics from '@overleaf/metrics'
  2. import Settings from '@overleaf/settings'
  3. import { callbackify } from 'node:util'
  4. import SafeExec from './SafeExec.js'
  5. import Errors from './Errors.js'
  6. const { ConversionError } = Errors
  7. const APPROVED_FORMATS = ['png']
  8. const FOURTY_SECONDS = 40 * 1000
  9. const KILL_SIGNAL = 'SIGTERM'
  10. export default {
  11. convert: callbackify(convert),
  12. thumbnail: callbackify(thumbnail),
  13. preview: callbackify(preview),
  14. promises: {
  15. convert,
  16. thumbnail,
  17. preview,
  18. },
  19. }
  20. async function convert(sourcePath, requestedFormat) {
  21. if (Settings.converter === 'pdftocairo') {
  22. const width = 1500
  23. return await _convert(sourcePath, requestedFormat, [
  24. 'pdftocairo',
  25. '-png',
  26. '-singlefile',
  27. '-scale-to-x',
  28. width.toString(),
  29. '-scale-to-y',
  30. '-1', // maintain aspect ratio
  31. sourcePath,
  32. ])
  33. } else {
  34. const width = '600x'
  35. return await _convert(sourcePath, requestedFormat, [
  36. 'convert',
  37. '-define',
  38. `pdf:fit-page=${width}`,
  39. '-flatten',
  40. '-density',
  41. '300',
  42. `${sourcePath}[0]`,
  43. ])
  44. }
  45. }
  46. async function thumbnail(sourcePath) {
  47. if (Settings.converter === 'pdftocairo') {
  48. const width = 700
  49. return await _convert(sourcePath, 'png', [
  50. 'pdftocairo',
  51. '-png',
  52. '-singlefile',
  53. '-scale-to-x',
  54. width.toString(),
  55. '-scale-to-y',
  56. '-1', // maintain aspect ratio
  57. sourcePath,
  58. ])
  59. } else {
  60. const width = '260x'
  61. return await convert(sourcePath, 'png', [
  62. 'convert',
  63. '-flatten',
  64. '-background',
  65. 'white',
  66. '-density',
  67. '300',
  68. '-define',
  69. `pdf:fit-page=${width}`,
  70. `${sourcePath}[0]`,
  71. '-resize',
  72. width,
  73. ])
  74. }
  75. }
  76. async function preview(sourcePath) {
  77. const width = 1000
  78. if (Settings.converter === 'pdftocairo') {
  79. return await _convert(sourcePath, 'png', [
  80. 'pdftocairo',
  81. '-png',
  82. '-singlefile',
  83. '-scale-to-x',
  84. width.toString(),
  85. '-scale-to-y',
  86. '-1', // maintain aspect ratio
  87. sourcePath,
  88. ])
  89. } else {
  90. return await convert(sourcePath, 'png', [
  91. 'convert',
  92. '-flatten',
  93. '-background',
  94. 'white',
  95. '-density',
  96. '300',
  97. '-define',
  98. `pdf:fit-page=${width}`,
  99. `${sourcePath}[0]`,
  100. '-resize',
  101. width,
  102. ])
  103. }
  104. }
  105. async function _convert(sourcePath, requestedFormat, command) {
  106. if (!APPROVED_FORMATS.includes(requestedFormat)) {
  107. throw new ConversionError('invalid format requested', {
  108. format: requestedFormat,
  109. })
  110. }
  111. const timer = new metrics.Timer('imageConvert')
  112. const destPath = `${sourcePath}.${requestedFormat}`
  113. command.push(Settings.converter === 'pdftocairo' ? sourcePath : destPath)
  114. command = Settings.commands.convertCommandPrefix.concat(command)
  115. try {
  116. await SafeExec.promises(command, {
  117. killSignal: KILL_SIGNAL,
  118. timeout: FOURTY_SECONDS,
  119. })
  120. } catch (err) {
  121. throw new ConversionError(
  122. 'something went wrong converting file',
  123. { stderr: err.stderr, sourcePath, requestedFormat, destPath },
  124. err
  125. )
  126. }
  127. timer.done()
  128. return destPath
  129. }