polotno-node 2.15.7 → 2.15.8
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.
- package/instance.js +78 -7
- package/package.json +1 -1
package/instance.js
CHANGED
|
@@ -2,6 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
5
6
|
const { pathToFileURL } = require('url');
|
|
6
7
|
const { Readable } = require('stream');
|
|
7
8
|
const { pipeline } = require('stream/promises');
|
|
@@ -603,8 +604,35 @@ module.exports.createInstance = async ({
|
|
|
603
604
|
return blob;
|
|
604
605
|
};
|
|
605
606
|
|
|
606
|
-
const
|
|
607
|
-
|
|
607
|
+
const isFfmpegAvailable = () => {
|
|
608
|
+
try {
|
|
609
|
+
execSync('ffmpeg -version', { stdio: 'ignore' });
|
|
610
|
+
return true;
|
|
611
|
+
} catch {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const getAudioCodec = (filePath) => {
|
|
617
|
+
try {
|
|
618
|
+
const result = execSync(
|
|
619
|
+
`ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "${filePath}"`,
|
|
620
|
+
{ encoding: 'utf8' }
|
|
621
|
+
);
|
|
622
|
+
return result.trim();
|
|
623
|
+
} catch {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const convertAudioToAac = (inputPath, outputPath) => {
|
|
629
|
+
execSync(
|
|
630
|
+
`ffmpeg -y -i "${inputPath}" -c:v copy -c:a aac "${outputPath}"`,
|
|
631
|
+
{ stdio: 'ignore' }
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const jsonToVideoFile = async (json, outputPath, attrs = {}) => {
|
|
608
636
|
const shouldDownloadMedia =
|
|
609
637
|
!hasCustomClientUrl && !(attrs && attrs.skipDownloads);
|
|
610
638
|
|
|
@@ -612,6 +640,8 @@ module.exports.createInstance = async ({
|
|
|
612
640
|
? await prepareLocalMediaForVideoExport(json)
|
|
613
641
|
: { json, cleanup: async () => {} };
|
|
614
642
|
|
|
643
|
+
const writeStream = fs.createWriteStream(outputPath);
|
|
644
|
+
|
|
615
645
|
try {
|
|
616
646
|
const mimeType = await run(
|
|
617
647
|
async (json, attrs) => {
|
|
@@ -708,7 +738,7 @@ module.exports.createInstance = async ({
|
|
|
708
738
|
for (let i = 0; i < chunk.length; i++) {
|
|
709
739
|
binary += String.fromCharCode(chunk[i]);
|
|
710
740
|
}
|
|
711
|
-
await window.
|
|
741
|
+
await window.__polotnoWriteChunk(btoa(binary));
|
|
712
742
|
}
|
|
713
743
|
|
|
714
744
|
return videoBlob.type;
|
|
@@ -717,19 +747,59 @@ module.exports.createInstance = async ({
|
|
|
717
747
|
{
|
|
718
748
|
...attrs,
|
|
719
749
|
exposeFunctions: {
|
|
720
|
-
|
|
750
|
+
__polotnoWriteChunk: (base64Chunk) => {
|
|
751
|
+
const buffer = Buffer.from(base64Chunk, 'base64');
|
|
752
|
+
writeStream.write(buffer);
|
|
753
|
+
},
|
|
721
754
|
},
|
|
722
755
|
}
|
|
723
756
|
);
|
|
724
|
-
|
|
757
|
+
|
|
758
|
+
writeStream.end();
|
|
759
|
+
await new Promise((resolve) => writeStream.on('finish', resolve));
|
|
760
|
+
|
|
761
|
+
// Convert audio to AAC if needed
|
|
762
|
+
if (isFfmpegAvailable()) {
|
|
763
|
+
const codec = getAudioCodec(outputPath);
|
|
764
|
+
if (codec && codec !== 'aac') {
|
|
765
|
+
const tempPath = outputPath + '.tmp.mp4';
|
|
766
|
+
convertAudioToAac(outputPath, tempPath);
|
|
767
|
+
await fs.promises.rename(tempPath, outputPath);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return mimeType;
|
|
725
772
|
} finally {
|
|
726
773
|
await prepared.cleanup();
|
|
727
774
|
}
|
|
728
775
|
};
|
|
729
776
|
|
|
777
|
+
const jsonToVideoDataURL = async (json, attrs) => {
|
|
778
|
+
const tempPath = path.join(
|
|
779
|
+
os.tmpdir(),
|
|
780
|
+
`polotno-video-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`
|
|
781
|
+
);
|
|
782
|
+
try {
|
|
783
|
+
const mimeType = await jsonToVideoFile(json, tempPath, attrs);
|
|
784
|
+
const buffer = await fs.promises.readFile(tempPath);
|
|
785
|
+
return `data:${mimeType || 'video/mp4'};base64,${buffer.toString('base64')}`;
|
|
786
|
+
} finally {
|
|
787
|
+
await fs.promises.rm(tempPath, { force: true });
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
730
791
|
const jsonToVideoBase64 = async (json, attrs) => {
|
|
731
|
-
const
|
|
732
|
-
|
|
792
|
+
const tempPath = path.join(
|
|
793
|
+
os.tmpdir(),
|
|
794
|
+
`polotno-video-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`
|
|
795
|
+
);
|
|
796
|
+
try {
|
|
797
|
+
await jsonToVideoFile(json, tempPath, attrs);
|
|
798
|
+
const buffer = await fs.promises.readFile(tempPath);
|
|
799
|
+
return buffer.toString('base64');
|
|
800
|
+
} finally {
|
|
801
|
+
await fs.promises.rm(tempPath, { force: true });
|
|
802
|
+
}
|
|
733
803
|
};
|
|
734
804
|
|
|
735
805
|
const instance = {
|
|
@@ -749,6 +819,7 @@ module.exports.createInstance = async ({
|
|
|
749
819
|
jsonToGIFBase64,
|
|
750
820
|
jsonToVideoDataURL,
|
|
751
821
|
jsonToVideoBase64,
|
|
822
|
+
jsonToVideoFile,
|
|
752
823
|
createPage: async () =>
|
|
753
824
|
await module.exports.createPage(browser, visitPage, requestInterceptor),
|
|
754
825
|
};
|