jbilcke-hf HF staff commited on
Commit
3165afb
β€’
1 Parent(s): 94c46c0

let's try dev mode

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. Dockerfile +11 -9
  2. package-lock.json +33 -139
  3. package.json +10 -18
  4. src/bug-in-bun/FIXME.md +10 -0
  5. src/{core/ffmpeg/getMediaInfo.mts β†’ bug-in-bun/aitube_ffmpeg/analyze/getMediaInfo.ts} +6 -6
  6. src/bug-in-bun/aitube_ffmpeg/analyze/index.ts +1 -0
  7. src/{core/ffmpeg/concatenateAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateAudio.ts} +23 -21
  8. src/{core/ffmpeg/concatenateVideos.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideos.ts} +13 -14
  9. src/{core/ffmpeg/concatenateVideosAndMergeAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosAndMergeAudio.ts} +9 -12
  10. src/{core/ffmpeg/concatenateVideosWithAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosWithAudio.ts} +17 -18
  11. src/{core/ffmpeg/createVideoFromFrames.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/createVideoFromFrames.ts} +3 -3
  12. src/bug-in-bun/aitube_ffmpeg/concatenate/index.ts +8 -0
  13. src/{core/ffmpeg/convertAudioToWav.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertAudioToWav.ts} +11 -10
  14. src/{core/ffmpeg/convertMp4ToMp3.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertMp4ToMp3.ts} +7 -6
  15. src/{core/ffmpeg/convertMp4ToWebm.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertMp4ToWebm.ts} +5 -6
  16. src/bug-in-bun/aitube_ffmpeg/convert/index.ts +3 -0
  17. src/bug-in-bun/aitube_ffmpeg/index.ts +38 -0
  18. src/{core/ffmpeg/addImageToVideo.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/addImageToVideo.ts} +7 -6
  19. src/{core/ffmpeg/addTextToVideo.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/addTextToVideo.ts} +4 -3
  20. src/{core/ffmpeg/createTextOverlayImage.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/createTextOverlayImage.ts} +2 -2
  21. src/{core/utils/getCssStyle.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/getCssStyle.ts} +0 -0
  22. src/{core/converters/htmlToBase64Png.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/htmlToBase64Png.ts} +3 -1
  23. src/{core/ffmpeg/imageToVideoBase64.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/imageToVideoBase64.ts} +19 -11
  24. src/bug-in-bun/aitube_ffmpeg/overlay/index.ts +6 -0
  25. src/{core/ffmpeg/cropBase64Video.mts β†’ bug-in-bun/aitube_ffmpeg/transform/cropBase64Video.ts} +6 -6
  26. src/{core/ffmpeg/cropVideo.mts β†’ bug-in-bun/aitube_ffmpeg/transform/cropVideo.ts} +8 -8
  27. src/bug-in-bun/aitube_ffmpeg/transform/index.ts +3 -0
  28. src/{core/ffmpeg/scaleVideo.mts β†’ bug-in-bun/aitube_ffmpeg/transform/scaleVideo.ts} +7 -10
  29. src/core/base64/addBase64.mts +0 -51
  30. src/core/base64/dataUriToBlob.mts +0 -15
  31. src/core/base64/extractBase64.mts +0 -39
  32. src/core/converters/blobToWebp.mts +0 -5
  33. src/core/converters/bufferToJpeg.mts +0 -5
  34. src/core/converters/bufferToMp3.mts +0 -5
  35. src/core/converters/bufferToMp4.mts +0 -5
  36. src/core/converters/bufferToPng.mts +0 -5
  37. src/core/converters/bufferToWav.mts +0 -5
  38. src/core/converters/bufferToWebp.mts +0 -5
  39. src/core/converters/convertImageTo.mts +0 -31
  40. src/core/converters/convertImageToJpeg.mts +0 -27
  41. src/core/converters/convertImageToOriginal.mts +0 -6
  42. src/core/converters/convertImageToPng.mts +0 -23
  43. src/core/converters/convertImageToWebp.mts +0 -41
  44. src/core/converters/imageFormats.mts +0 -1
  45. src/core/exporters/{clapWithStoryboardsToVideoFile.mts β†’ clapWithStoryboardsToVideoFile.ts} +2 -2
  46. src/core/exporters/{clapWithVideosToVideoFile.mts β†’ clapWithVideosToVideoFile.ts} +2 -3
  47. src/core/exporters/{storyboardSegmentToVideoFile.mts β†’ storyboardSegmentToVideoFile.ts} +5 -7
  48. src/core/exporters/{videoSegmentToVideoFile.mts β†’ videoSegmentToVideoFile.ts} +5 -7
  49. src/core/files/deleteFile.mts +0 -14
  50. src/core/files/deleteFileWithName.mts +0 -12
Dockerfile CHANGED
@@ -5,6 +5,9 @@ ARG DEBIAN_FRONTEND=noninteractive
5
 
6
  RUN apk update
7
 
 
 
 
8
  RUN apk add alpine-sdk pkgconfig
9
 
10
  # For FFMPEG and gl concat
@@ -16,32 +19,31 @@ RUN apk add build-base gcompat udev ttf-opensans chromium
16
  RUN apk add ffmpeg
17
 
18
  # Set up a new user named "user" with user ID 1000
19
- RUN adduser --disabled-password --uid 1001 user
20
 
21
  # Switch to the "user" user
22
  USER user
23
 
24
  # Set home to the user's home directory
25
- ENV HOME=/home/user \
26
- PATH=/home/user/.local/bin:$PATH
27
 
28
  # Set the working directory to the user's home directory
29
- WORKDIR $HOME/app
30
 
31
  # Install app dependencies
32
  # A wildcard is used to ensure both package.json AND package-lock.json are copied
33
  # where available (npm@5+)
34
- COPY --chown=user package*.json $HOME/app
35
 
36
  # make sure the .env is copied as well
37
- COPY --chown=user .env $HOME/app
38
 
39
  RUN ffmpeg -version
40
 
41
- RUN npm install
 
42
 
43
- # Copy the current directory contents into the container at $HOME/app setting the owner to the user
44
- COPY --chown=user . $HOME/app
45
 
46
  EXPOSE 7860
47
 
 
5
 
6
  RUN apk update
7
 
8
+ # for dev mode
9
+ RUN apk add git git-lfs get procps htop vim nano
10
+
11
  RUN apk add alpine-sdk pkgconfig
12
 
13
  # For FFMPEG and gl concat
 
19
  RUN apk add ffmpeg
20
 
21
  # Set up a new user named "user" with user ID 1000
22
+ RUN adduser --disabled-password --uid 1000 user
23
 
24
  # Switch to the "user" user
25
  USER user
26
 
27
  # Set home to the user's home directory
28
+ ENV PATH=.local/bin:$PATH
 
29
 
30
  # Set the working directory to the user's home directory
31
+ WORKDIR /app
32
 
33
  # Install app dependencies
34
  # A wildcard is used to ensure both package.json AND package-lock.json are copied
35
  # where available (npm@5+)
36
+ COPYY --link --chown=user package*.json /app
37
 
38
  # make sure the .env is copied as well
39
+ COPY --link --chown=user .env /app
40
 
41
  RUN ffmpeg -version
42
 
43
+ # Copy the current directory contents into the container at /app setting the owner to the user
44
+ COPY --link --chown=user . $HOME/app
45
 
46
+ RUN npm ci
 
47
 
48
  EXPOSE 7860
49
 
package-lock.json CHANGED
@@ -10,27 +10,20 @@
10
  "license": "Apache License",
11
  "dependencies": {
12
  "@aitube/clap": "0.0.7",
 
 
13
  "@types/express": "^4.17.17",
14
  "@types/fluent-ffmpeg": "^2.1.24",
15
  "@types/uuid": "^9.0.2",
16
  "dotenv": "^16.3.1",
17
- "eventsource-parser": "^1.0.0",
18
  "express": "^4.18.2",
19
  "fluent-ffmpeg": "^2.1.2",
20
- "fs-extra": "^11.1.1",
21
- "mime-types": "^2.1.35",
22
- "node-fetch": "^3.3.1",
23
- "puppeteer": "^22.7.0",
24
  "query-string": "^9.0.0",
25
  "sharp": "^0.33.3",
26
- "temp-dir": "^3.0.0",
27
- "ts-node": "^10.9.1",
28
- "type-fest": "^4.8.2",
29
- "uuid": "^9.0.0",
30
- "yaml": "^2.4.1"
31
  },
32
  "devDependencies": {
33
- "@types/mime-types": "^2.1.4",
34
  "@types/node": "^20.12.7",
35
  "tsx": "^4.7.0"
36
  }
@@ -47,6 +40,24 @@
47
  "typescript": "^5.4.5"
48
  }
49
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  "node_modules/@babel/code-frame": {
51
  "version": "7.24.2",
52
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
@@ -60,19 +71,19 @@
60
  }
61
  },
62
  "node_modules/@babel/helper-validator-identifier": {
63
- "version": "7.22.20",
64
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
65
- "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
66
  "engines": {
67
  "node": ">=6.9.0"
68
  }
69
  },
70
  "node_modules/@babel/highlight": {
71
- "version": "7.24.2",
72
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
73
- "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
74
  "dependencies": {
75
- "@babel/helper-validator-identifier": "^7.22.20",
76
  "chalk": "^2.4.2",
77
  "js-tokens": "^4.0.0",
78
  "picocolors": "^1.0.0"
@@ -1046,12 +1057,6 @@
1046
  "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
1047
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
1048
  },
1049
- "node_modules/@types/mime-types": {
1050
- "version": "2.1.4",
1051
- "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz",
1052
- "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==",
1053
- "dev": true
1054
- },
1055
  "node_modules/@types/node": {
1056
  "version": "20.12.7",
1057
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
@@ -1529,11 +1534,11 @@
1529
  "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
1530
  },
1531
  "node_modules/data-uri-to-buffer": {
1532
- "version": "4.0.1",
1533
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
1534
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
1535
  "engines": {
1536
- "node": ">= 12"
1537
  }
1538
  },
1539
  "node_modules/debug": {
@@ -1806,14 +1811,6 @@
1806
  "node": ">= 0.6"
1807
  }
1808
  },
1809
- "node_modules/eventsource-parser": {
1810
- "version": "1.1.2",
1811
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
1812
- "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
1813
- "engines": {
1814
- "node": ">=14.18"
1815
- }
1816
- },
1817
  "node_modules/express": {
1818
  "version": "4.19.2",
1819
  "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
@@ -1908,28 +1905,6 @@
1908
  "pend": "~1.2.0"
1909
  }
1910
  },
1911
- "node_modules/fetch-blob": {
1912
- "version": "3.2.0",
1913
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
1914
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
1915
- "funding": [
1916
- {
1917
- "type": "github",
1918
- "url": "https://github.com/sponsors/jimmywarting"
1919
- },
1920
- {
1921
- "type": "paypal",
1922
- "url": "https://paypal.me/jimmywarting"
1923
- }
1924
- ],
1925
- "dependencies": {
1926
- "node-domexception": "^1.0.0",
1927
- "web-streams-polyfill": "^3.0.3"
1928
- },
1929
- "engines": {
1930
- "node": "^12.20 || >= 14.13"
1931
- }
1932
- },
1933
  "node_modules/filter-obj": {
1934
  "version": "5.1.0",
1935
  "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
@@ -1970,17 +1945,6 @@
1970
  "node": ">=0.8.0"
1971
  }
1972
  },
1973
- "node_modules/formdata-polyfill": {
1974
- "version": "4.0.10",
1975
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
1976
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
1977
- "dependencies": {
1978
- "fetch-blob": "^3.1.2"
1979
- },
1980
- "engines": {
1981
- "node": ">=12.20.0"
1982
- }
1983
- },
1984
  "node_modules/forwarded": {
1985
  "version": "0.2.0",
1986
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -2098,14 +2062,6 @@
2098
  "node": ">= 14"
2099
  }
2100
  },
2101
- "node_modules/get-uri/node_modules/data-uri-to-buffer": {
2102
- "version": "6.0.2",
2103
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
2104
- "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
2105
- "engines": {
2106
- "node": ">= 14"
2107
- }
2108
- },
2109
  "node_modules/get-uri/node_modules/debug": {
2110
  "version": "4.3.4",
2111
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2496,41 +2452,6 @@
2496
  "node": ">= 0.4.0"
2497
  }
2498
  },
2499
- "node_modules/node-domexception": {
2500
- "version": "1.0.0",
2501
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
2502
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
2503
- "funding": [
2504
- {
2505
- "type": "github",
2506
- "url": "https://github.com/sponsors/jimmywarting"
2507
- },
2508
- {
2509
- "type": "github",
2510
- "url": "https://paypal.me/jimmywarting"
2511
- }
2512
- ],
2513
- "engines": {
2514
- "node": ">=10.5.0"
2515
- }
2516
- },
2517
- "node_modules/node-fetch": {
2518
- "version": "3.3.2",
2519
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
2520
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
2521
- "dependencies": {
2522
- "data-uri-to-buffer": "^4.0.0",
2523
- "fetch-blob": "^3.1.4",
2524
- "formdata-polyfill": "^4.0.10"
2525
- },
2526
- "engines": {
2527
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
2528
- },
2529
- "funding": {
2530
- "type": "opencollective",
2531
- "url": "https://opencollective.com/node-fetch"
2532
- }
2533
- },
2534
  "node_modules/object-inspect": {
2535
  "version": "1.13.1",
2536
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -3217,14 +3138,6 @@
3217
  "streamx": "^2.15.0"
3218
  }
3219
  },
3220
- "node_modules/temp-dir": {
3221
- "version": "3.0.0",
3222
- "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz",
3223
- "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==",
3224
- "engines": {
3225
- "node": ">=14.16"
3226
- }
3227
- },
3228
  "node_modules/through": {
3229
  "version": "2.3.8",
3230
  "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -3304,17 +3217,6 @@
3304
  "fsevents": "~2.3.3"
3305
  }
3306
  },
3307
- "node_modules/type-fest": {
3308
- "version": "4.18.0",
3309
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.0.tgz",
3310
- "integrity": "sha512-+dbmiyliDY/2TTcjCS7NpI9yV2iEFlUDk5TKnsbkN7ZoRu5s7bT+zvYtNFhFXC2oLwURGT2frACAZvbbyNBI+w==",
3311
- "engines": {
3312
- "node": ">=16"
3313
- },
3314
- "funding": {
3315
- "url": "https://github.com/sponsors/sindresorhus"
3316
- }
3317
- },
3318
  "node_modules/type-is": {
3319
  "version": "1.6.18",
3320
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -3408,14 +3310,6 @@
3408
  "node": ">= 0.8"
3409
  }
3410
  },
3411
- "node_modules/web-streams-polyfill": {
3412
- "version": "3.3.3",
3413
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
3414
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
3415
- "engines": {
3416
- "node": ">= 8"
3417
- }
3418
- },
3419
  "node_modules/which": {
3420
  "version": "1.3.1",
3421
  "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
 
10
  "license": "Apache License",
11
  "dependencies": {
12
  "@aitube/clap": "0.0.7",
13
+ "@aitube/encoders": "0.0.0",
14
+ "@aitube/io": "0.0.0",
15
  "@types/express": "^4.17.17",
16
  "@types/fluent-ffmpeg": "^2.1.24",
17
  "@types/uuid": "^9.0.2",
18
  "dotenv": "^16.3.1",
 
19
  "express": "^4.18.2",
20
  "fluent-ffmpeg": "^2.1.2",
21
+ "puppeteer": "^22.7.1",
 
 
 
22
  "query-string": "^9.0.0",
23
  "sharp": "^0.33.3",
24
+ "ts-node": "^10.9.1"
 
 
 
 
25
  },
26
  "devDependencies": {
 
27
  "@types/node": "^20.12.7",
28
  "tsx": "^4.7.0"
29
  }
 
40
  "typescript": "^5.4.5"
41
  }
42
  },
43
+ "node_modules/@aitube/encoders": {
44
+ "version": "0.0.0",
45
+ "resolved": "https://registry.npmjs.org/@aitube/encoders/-/encoders-0.0.0.tgz",
46
+ "integrity": "sha512-jKIii0m0Lwr6l+slA9cPg/c8WCl2uylNYON74VU+3cgi//7Yt34Ym1afGIjQd2vEWzPR7LrlHcrb4F0War7Fmg==",
47
+ "peerDependencies": {
48
+ "typescript": "^5.4.5"
49
+ }
50
+ },
51
+ "node_modules/@aitube/io": {
52
+ "version": "0.0.0",
53
+ "resolved": "https://registry.npmjs.org/@aitube/io/-/io-0.0.0.tgz",
54
+ "integrity": "sha512-ay/h4VZGmlS3ZHraHHLkeXovXEv6WU2SSZp+n27rgasnoDsAU8If2IXgZXxIjnpqOigZ8qed/GhmCzvhqvmOrQ==",
55
+ "dependencies": {
56
+ "mime-types": "^2.1.35",
57
+ "sharp": "^0.33.3",
58
+ "uuid": "^9.0.1"
59
+ }
60
+ },
61
  "node_modules/@babel/code-frame": {
62
  "version": "7.24.2",
63
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
 
71
  }
72
  },
73
  "node_modules/@babel/helper-validator-identifier": {
74
+ "version": "7.24.5",
75
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
76
+ "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
77
  "engines": {
78
  "node": ">=6.9.0"
79
  }
80
  },
81
  "node_modules/@babel/highlight": {
82
+ "version": "7.24.5",
83
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
84
+ "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
85
  "dependencies": {
86
+ "@babel/helper-validator-identifier": "^7.24.5",
87
  "chalk": "^2.4.2",
88
  "js-tokens": "^4.0.0",
89
  "picocolors": "^1.0.0"
 
1057
  "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
1058
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
1059
  },
 
 
 
 
 
 
1060
  "node_modules/@types/node": {
1061
  "version": "20.12.7",
1062
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
 
1534
  "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
1535
  },
1536
  "node_modules/data-uri-to-buffer": {
1537
+ "version": "6.0.2",
1538
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
1539
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
1540
  "engines": {
1541
+ "node": ">= 14"
1542
  }
1543
  },
1544
  "node_modules/debug": {
 
1811
  "node": ">= 0.6"
1812
  }
1813
  },
 
 
 
 
 
 
 
 
1814
  "node_modules/express": {
1815
  "version": "4.19.2",
1816
  "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
 
1905
  "pend": "~1.2.0"
1906
  }
1907
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1908
  "node_modules/filter-obj": {
1909
  "version": "5.1.0",
1910
  "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
 
1945
  "node": ">=0.8.0"
1946
  }
1947
  },
 
 
 
 
 
 
 
 
 
 
 
1948
  "node_modules/forwarded": {
1949
  "version": "0.2.0",
1950
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
 
2062
  "node": ">= 14"
2063
  }
2064
  },
 
 
 
 
 
 
 
 
2065
  "node_modules/get-uri/node_modules/debug": {
2066
  "version": "4.3.4",
2067
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 
2452
  "node": ">= 0.4.0"
2453
  }
2454
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2455
  "node_modules/object-inspect": {
2456
  "version": "1.13.1",
2457
  "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
 
3138
  "streamx": "^2.15.0"
3139
  }
3140
  },
 
 
 
 
 
 
 
 
3141
  "node_modules/through": {
3142
  "version": "2.3.8",
3143
  "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
 
3217
  "fsevents": "~2.3.3"
3218
  }
3219
  },
 
 
 
 
 
 
 
 
 
 
 
3220
  "node_modules/type-is": {
3221
  "version": "1.6.18",
3222
  "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
 
3310
  "node": ">= 0.8"
3311
  }
3312
  },
 
 
 
 
 
 
 
 
3313
  "node_modules/which": {
3314
  "version": "1.3.1",
3315
  "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
package.json CHANGED
@@ -1,41 +1,33 @@
1
  {
2
  "name": "ai-tube-clap-exporter",
3
  "version": "1.0.0",
4
- "description": "A service to convert a .clap (will all its assets) to a video file",
5
- "main": "src/index.mts",
6
  "scripts": {
7
- "start": "tsx src/index.mts",
8
- "dev": "tsx src/index.mts",
9
  "docker": "npm run docker:build && npm run docker:run",
10
- "docker:build": "docker build -t ai-tube-robot .",
11
- "docker:run": "docker run -it -p 7860:7860 ai-tube-robot",
12
- "alchemy:test": "tsx src/core/alchemy/test.mts"
13
  },
14
  "author": "Julian Bilcke <[email protected]>",
15
  "license": "Apache License",
16
  "dependencies": {
17
  "@aitube/clap": "0.0.7",
 
 
18
  "@types/express": "^4.17.17",
19
  "@types/fluent-ffmpeg": "^2.1.24",
20
  "@types/uuid": "^9.0.2",
21
  "dotenv": "^16.3.1",
22
- "eventsource-parser": "^1.0.0",
23
  "express": "^4.18.2",
24
  "fluent-ffmpeg": "^2.1.2",
25
- "fs-extra": "^11.1.1",
26
- "mime-types": "^2.1.35",
27
- "node-fetch": "^3.3.1",
28
- "puppeteer": "^22.7.0",
29
  "query-string": "^9.0.0",
30
  "sharp": "^0.33.3",
31
- "temp-dir": "^3.0.0",
32
- "ts-node": "^10.9.1",
33
- "type-fest": "^4.8.2",
34
- "uuid": "^9.0.0",
35
- "yaml": "^2.4.1"
36
  },
37
  "devDependencies": {
38
- "@types/mime-types": "^2.1.4",
39
  "@types/node": "^20.12.7",
40
  "tsx": "^4.7.0"
41
  }
 
1
  {
2
  "name": "ai-tube-clap-exporter",
3
  "version": "1.0.0",
4
+ "description": "API service to convert a .clap (will all its assets) to a video file",
5
+ "main": "src/index.ts",
6
  "scripts": {
7
+ "start": "tsx src/index.ts",
8
+ "dev": "tsx src/index.ts",
9
  "docker": "npm run docker:build && npm run docker:run",
10
+ "docker:build": "docker build -t ai-tube-clap-exporter .",
11
+ "docker:run": "docker run -it -p 7860:7860 ai-tube-clap-exporter"
 
12
  },
13
  "author": "Julian Bilcke <[email protected]>",
14
  "license": "Apache License",
15
  "dependencies": {
16
  "@aitube/clap": "0.0.7",
17
+ "@aitube/encoders": "0.0.0",
18
+ "@aitube/io": "0.0.0",
19
  "@types/express": "^4.17.17",
20
  "@types/fluent-ffmpeg": "^2.1.24",
21
  "@types/uuid": "^9.0.2",
22
  "dotenv": "^16.3.1",
 
23
  "express": "^4.18.2",
24
  "fluent-ffmpeg": "^2.1.2",
25
+ "puppeteer": "^22.7.1",
 
 
 
26
  "query-string": "^9.0.0",
27
  "sharp": "^0.33.3",
28
+ "ts-node": "^10.9.1"
 
 
 
 
29
  },
30
  "devDependencies": {
 
31
  "@types/node": "^20.12.7",
32
  "tsx": "^4.7.0"
33
  }
src/bug-in-bun/FIXME.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ We should use import .... from "@aitube/ffmpeg"
2
+
3
+ But there is a bug with Bun:
4
+
5
+ https://github.com/oven-sh/bun/issues/4477
6
+
7
+
8
+ Once fixed, we can delete aitube_ffmpeg (and the dependencies like fluent-ffmpeg, puppeteer etc)
9
+
10
+ and only use @aitube/ffmpeg
src/{core/ffmpeg/getMediaInfo.mts β†’ bug-in-bun/aitube_ffmpeg/analyze/getMediaInfo.ts} RENAMED
@@ -1,8 +1,8 @@
1
- import ffmpeg from "fluent-ffmpeg";
 
 
2
 
3
- import { tmpdir } from "node:os";
4
- import { promises as fs } from "node:fs";
5
- import { join } from "node:path";
6
 
7
  export type MediaMetadata = {
8
  durationInSec: number;
@@ -31,13 +31,13 @@ export async function getMediaInfo(input: string): Promise<MediaMetadata> {
31
  const tempFileName = join(tmpdir(), `temp-media-${Date.now()}`);
32
 
33
  // Write the buffer to a temporary file
34
- await fs.writeFile(tempFileName, buffer);
35
 
36
  // Get metadata from the temporary file then delete the file
37
  try {
38
  return await getMetaDataFromPath(tempFileName);
39
  } finally {
40
- await fs.unlink(tempFileName);
41
  }
42
  }
43
 
 
1
+ import { tmpdir } from "node:os"
2
+ import { writeFile, rm } from "node:fs/promises"
3
+ import { join } from "node:path"
4
 
5
+ import ffmpeg from "fluent-ffmpeg"
 
 
6
 
7
  export type MediaMetadata = {
8
  durationInSec: number;
 
31
  const tempFileName = join(tmpdir(), `temp-media-${Date.now()}`);
32
 
33
  // Write the buffer to a temporary file
34
+ await writeFile(tempFileName, buffer);
35
 
36
  // Get metadata from the temporary file then delete the file
37
  try {
38
  return await getMetaDataFromPath(tempFileName);
39
  } finally {
40
+ await rm(tempFileName);
41
  }
42
  }
43
 
src/bug-in-bun/aitube_ffmpeg/analyze/index.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ export { getMediaInfo } from "./getMediaInfo"
src/{core/ffmpeg/concatenateAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateAudio.ts} RENAMED
@@ -1,13 +1,12 @@
1
- import { existsSync, promises as fs } from "node:fs"
2
- import os from "node:os"
3
  import path from "node:path"
4
 
5
- import { v4 as uuidv4 } from "uuid";
6
- import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg";
7
- import { writeBase64ToFile } from "../files/writeBase64ToFile.mts";
8
- import { getMediaInfo } from "./getMediaInfo.mts";
9
- import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
10
- import { addBase64Header } from "../base64/addBase64.mts";
11
 
12
  export type ConcatenateAudioOptions = {
13
  // those are base64 audio strings!
@@ -34,20 +33,19 @@ export async function concatenateAudio({
34
  throw new Error("Audios must be provided in an array");
35
  }
36
 
37
- const tempDir = path.join(os.tmpdir(), uuidv4());
38
- await fs.mkdir(tempDir);
39
 
40
  // console.log(" |- created tmp dir")
41
 
42
  // trivial case: there is only one audio to concatenate!
43
  if (audioTracks.length === 1 && audioTracks[0]) {
44
  const audioTrack = audioTracks[0]
45
- const outputFilePath = path.join(tempDir, `audio_0.${outputFormat}`);
46
- await writeBase64ToFile(addBase64Header(audioTrack, "wav"), outputFilePath);
47
 
48
  // console.log(" |- there is only one track! so.. returning that")
49
- const { durationInSec } = await getMediaInfo(outputFilePath);
50
- return { filepath: outputFilePath, durationInSec };
51
  }
52
 
53
  if (audioFilePaths.length === 1) {
@@ -60,10 +58,12 @@ export async function concatenateAudio({
60
  for (const track of audioTracks) {
61
  if (!track) { continue }
62
  const audioFilePath = path.join(tempDir, `audio_${++i}.wav`);
63
- await writeBase64ToFile(addBase64Header(track, "wav"), audioFilePath);
 
64
  audioFilePaths.push(audioFilePath);
65
  }
66
 
 
67
  audioFilePaths = audioFilePaths.filter((audio) => existsSync(audio))
68
 
69
  const outputFilePath = output ?? path.join(tempDir, `${uuidv4()}.${outputFormat}`);
@@ -77,7 +77,7 @@ export async function concatenateAudio({
77
  prevLabel = nextLabel;
78
  }
79
 
80
-
81
  console.log(" |- concatenateAudio(): DEBUG:", {
82
  tempDir,
83
  audioFilePaths,
@@ -85,13 +85,13 @@ export async function concatenateAudio({
85
  filterComplex,
86
  prevLabel
87
  })
 
88
 
89
  let cmd: FfmpegCommand = ffmpeg() // .outputOptions('-vn');
90
 
91
  audioFilePaths.forEach((audio, i) => {
92
- cmd = cmd.input(audio);
93
- });
94
-
95
 
96
  const promise = new Promise<ConcatenateAudioOutput>((resolve, reject) => {
97
  cmd = cmd
@@ -99,10 +99,12 @@ export async function concatenateAudio({
99
  .on('end', async () => {
100
  try {
101
  const { durationInSec } = await getMediaInfo(outputFilePath);
 
102
  // console.log("concatenation ended! see ->", outputFilePath)
103
- resolve({ filepath: outputFilePath, durationInSec });
 
104
  } catch (err) {
105
- reject(err);
106
  }
107
  })
108
  .complexFilter(filterComplex, prevLabel)
 
1
+ import { existsSync } from "node:fs"
 
2
  import path from "node:path"
3
 
4
+ import { v4 as uuidv4 } from "uuid"
5
+ import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg"
6
+ import { getRandomDirectory, removeTemporaryFiles, writeBase64ToFile } from "@aitube/io"
7
+ import { addBase64Header } from "@aitube/encoders"
8
+
9
+ import { getMediaInfo } from "../analyze/getMediaInfo"
10
 
11
  export type ConcatenateAudioOptions = {
12
  // those are base64 audio strings!
 
33
  throw new Error("Audios must be provided in an array");
34
  }
35
 
36
+ const tempDir = await getRandomDirectory()
 
37
 
38
  // console.log(" |- created tmp dir")
39
 
40
  // trivial case: there is only one audio to concatenate!
41
  if (audioTracks.length === 1 && audioTracks[0]) {
42
  const audioTrack = audioTracks[0]
43
+ const outputFilePath = path.join(tempDir, `audio_0.${outputFormat}`)
44
+ await writeBase64ToFile(addBase64Header(audioTrack, "wav"), outputFilePath)
45
 
46
  // console.log(" |- there is only one track! so.. returning that")
47
+ const { durationInSec } = await getMediaInfo(outputFilePath)
48
+ return { filepath: outputFilePath, durationInSec }
49
  }
50
 
51
  if (audioFilePaths.length === 1) {
 
58
  for (const track of audioTracks) {
59
  if (!track) { continue }
60
  const audioFilePath = path.join(tempDir, `audio_${++i}.wav`);
61
+ await writeBase64ToFile(addBase64Header(track, "wav"), audioFilePath)
62
+
63
  audioFilePaths.push(audioFilePath);
64
  }
65
 
66
+ // TODO: convert this to an async filter using promises
67
  audioFilePaths = audioFilePaths.filter((audio) => existsSync(audio))
68
 
69
  const outputFilePath = output ?? path.join(tempDir, `${uuidv4()}.${outputFormat}`);
 
77
  prevLabel = nextLabel;
78
  }
79
 
80
+ /*
81
  console.log(" |- concatenateAudio(): DEBUG:", {
82
  tempDir,
83
  audioFilePaths,
 
85
  filterComplex,
86
  prevLabel
87
  })
88
+ */
89
 
90
  let cmd: FfmpegCommand = ffmpeg() // .outputOptions('-vn');
91
 
92
  audioFilePaths.forEach((audio, i) => {
93
+ cmd = cmd.input(audio)
94
+ })
 
95
 
96
  const promise = new Promise<ConcatenateAudioOutput>((resolve, reject) => {
97
  cmd = cmd
 
99
  .on('end', async () => {
100
  try {
101
  const { durationInSec } = await getMediaInfo(outputFilePath);
102
+
103
  // console.log("concatenation ended! see ->", outputFilePath)
104
+ resolve({ filepath: outputFilePath, durationInSec })
105
+
106
  } catch (err) {
107
+ reject(err)
108
  }
109
  })
110
  .complexFilter(filterComplex, prevLabel)
src/{core/ffmpeg/concatenateVideos.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideos.ts} RENAMED
@@ -1,11 +1,11 @@
1
- import { existsSync, promises as fs } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
 
5
- import { v4 as uuidv4 } from "uuid";
6
- import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg";
 
7
 
8
- import { getMediaInfo } from "./getMediaInfo.mts";
9
 
10
  export type ConcatenateVideoOutput = {
11
  filepath: string;
@@ -22,22 +22,21 @@ export async function concatenateVideos({
22
  videoFilePaths: string[];
23
  }): Promise<ConcatenateVideoOutput> {
24
  if (!Array.isArray(videoFilePaths)) {
25
- throw new Error("Videos must be provided in an array");
26
  }
27
 
28
  videoFilePaths = videoFilePaths.filter((videoPath) => existsSync(videoPath))
29
 
30
  // Create a temporary working directory
31
- const tempDir = path.join(os.tmpdir(), uuidv4());
32
- await fs.mkdir(tempDir);
33
 
34
- const filePath = output ? output : path.join(tempDir, `${uuidv4()}.mp4`);
35
 
36
  if (!filePath) {
37
- throw new Error("Failed to generate a valid temporary file path");
38
  }
39
 
40
- let cmd: FfmpegCommand = ffmpeg();
41
 
42
  videoFilePaths.forEach((video) => {
43
  cmd = cmd.addInput(video)
@@ -57,5 +56,5 @@ export async function concatenateVideos({
57
  })
58
  .mergeToFile(filePath, tempDir);
59
  }
60
- );
61
- };
 
1
+ import { existsSync } from "node:fs"
2
+ import path from "node:path"
 
3
 
4
+ import { v4 as uuidv4 } from "uuid"
5
+ import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg"
6
+ import { getRandomDirectory } from "@aitube/io"
7
 
8
+ import { getMediaInfo } from "../analyze/getMediaInfo"
9
 
10
  export type ConcatenateVideoOutput = {
11
  filepath: string;
 
22
  videoFilePaths: string[];
23
  }): Promise<ConcatenateVideoOutput> {
24
  if (!Array.isArray(videoFilePaths)) {
25
+ throw new Error("Videos must be provided in an array")
26
  }
27
 
28
  videoFilePaths = videoFilePaths.filter((videoPath) => existsSync(videoPath))
29
 
30
  // Create a temporary working directory
31
+ const tempDir = await getRandomDirectory()
 
32
 
33
+ const filePath = output ? output : path.join(tempDir, `${uuidv4()}.mp4`)
34
 
35
  if (!filePath) {
36
+ throw new Error("Failed to generate a valid temporary file path")
37
  }
38
 
39
+ let cmd: FfmpegCommand = ffmpeg()
40
 
41
  videoFilePaths.forEach((video) => {
42
  cmd = cmd.addInput(video)
 
56
  })
57
  .mergeToFile(filePath, tempDir);
58
  }
59
+ )
60
+ }
src/{core/ffmpeg/concatenateVideosAndMergeAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosAndMergeAudio.ts} RENAMED
@@ -1,15 +1,13 @@
1
- import { existsSync, promises as fs } from "node:fs"
2
- import os from "node:os"
3
  import path from "node:path"
4
 
5
- import { v4 as uuidv4 } from "uuid";
6
- import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg";
7
- import { concatenateVideos } from "./concatenateVideos.mts";
8
- import { writeBase64ToFile } from "../files/writeBase64ToFile.mts";
9
- import { getMediaInfo } from "./getMediaInfo.mts";
10
- import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
11
- import { addBase64Header } from "../base64/addBase64.mts";
12
- import { extractBase64 } from "../base64/extractBase64.mts";
13
 
14
  type ConcatenateVideoAndMergeAudioOptions = {
15
  output?: string;
@@ -36,8 +34,7 @@ export const concatenateVideosAndMergeAudio = async ({
36
 
37
  try {
38
  // Prepare temporary directories
39
- const tempDir = path.join(os.tmpdir(), uuidv4());
40
- await fs.mkdir(tempDir);
41
 
42
  let i = 0
43
  for (const audioTrack of audioTracks) {
 
1
+ import { existsSync } from "node:fs"
 
2
  import path from "node:path"
3
 
4
+ import { v4 as uuidv4 } from "uuid"
5
+ import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg"
6
+ import { getRandomDirectory, removeTemporaryFiles, writeBase64ToFile } from "@aitube/io"
7
+ import { addBase64Header, extractBase64 } from "@aitube/encoders"
8
+
9
+ import { getMediaInfo } from "../analyze/getMediaInfo"
10
+ import { concatenateVideos } from "./concatenateVideos"
 
11
 
12
  type ConcatenateVideoAndMergeAudioOptions = {
13
  output?: string;
 
34
 
35
  try {
36
  // Prepare temporary directories
37
+ const tempDir = await getRandomDirectory()
 
38
 
39
  let i = 0
40
  for (const audioTrack of audioTracks) {
src/{core/ffmpeg/concatenateVideosWithAudio.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/concatenateVideosWithAudio.ts} RENAMED
@@ -1,15 +1,14 @@
1
- import { existsSync, promises as fs } from "node:fs"
2
- import os from "node:os"
3
  import path from "node:path"
4
 
5
- import { v4 as uuidv4 } from "uuid";
6
- import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg";
7
- import { concatenateVideos } from "./concatenateVideos.mts";
8
- import { writeBase64ToFile } from "../files/writeBase64ToFile.mts";
9
- import { getMediaInfo } from "./getMediaInfo.mts";
10
- import { removeTemporaryFiles } from "../files/removeTmpFiles.mts";
11
- import { addBase64Header } from "../base64/addBase64.mts";
12
- import { extractBase64 } from "../base64/extractBase64.mts";
13
 
14
  export type SupportedExportFormat = "mp4" | "webm"
15
  export const defaultExportFormat = "mp4"
@@ -41,14 +40,14 @@ export const concatenateVideosWithAudio = async ({
41
 
42
  try {
43
  // Prepare temporary directories
44
- const tempDir = path.join(os.tmpdir(), uuidv4());
45
- await fs.mkdir(tempDir);
46
 
47
  if (audioTrack && audioTrack.length > 0) {
48
  const analysis = extractBase64(audioTrack)
49
  // console.log(`concatenateVideosWithAudio: writing down an audio file (${analysis.extension}) from the supplied base64 track`)
50
- audioFilePath = path.join(tempDir, `audio.${analysis.extension}`);
51
- await writeBase64ToFile(addBase64Header(audioTrack, analysis.extension), audioFilePath);
 
52
  }
53
 
54
  // Decode and concatenate base64 video tracks to temporary file
@@ -58,13 +57,13 @@ export const concatenateVideosWithAudio = async ({
58
  // note: here we assume the input video is in mp4
59
 
60
  const analysis = extractBase64(audioTrack)
61
- const videoFilePath = path.join(tempDir, `video${++i}.${analysis.extension}`);
62
 
63
  // console.log(`concatenateVideosWithAudio: writing down a video file (${analysis.extension}) from the supplied base64 track`)
64
 
65
- await writeBase64ToFile(addBase64Header(track, analysis.extension), videoFilePath);
66
 
67
- videoFilePaths.push(videoFilePath);
68
  }
69
 
70
  videoFilePaths = videoFilePaths.filter((video) => existsSync(video))
@@ -162,7 +161,7 @@ export const concatenateVideosWithAudio = async ({
162
  try {
163
  if (asBase64) {
164
  try {
165
- const outputBuffer = await fs.readFile(finalOutputFilePath);
166
  const outputBase64 = addBase64Header(outputBuffer.toString("base64"), format)
167
  resolve(outputBase64);
168
  } catch (error) {
 
1
+ import { existsSync } from "node:fs"
2
+ import { readFile } from "node:fs/promises"
3
  import path from "node:path"
4
 
5
+ import { v4 as uuidv4 } from "uuid"
6
+ import ffmpeg, { FfmpegCommand } from "fluent-ffmpeg"
7
+ import { addBase64Header, extractBase64 } from "@aitube/encoders"
8
+ import { getRandomDirectory, removeTemporaryFiles, writeBase64ToFile } from "@aitube/io"
9
+
10
+ import { getMediaInfo } from "../analyze/getMediaInfo"
11
+ import { concatenateVideos } from "./concatenateVideos"
 
12
 
13
  export type SupportedExportFormat = "mp4" | "webm"
14
  export const defaultExportFormat = "mp4"
 
40
 
41
  try {
42
  // Prepare temporary directories
43
+ const tempDir = await getRandomDirectory()
 
44
 
45
  if (audioTrack && audioTrack.length > 0) {
46
  const analysis = extractBase64(audioTrack)
47
  // console.log(`concatenateVideosWithAudio: writing down an audio file (${analysis.extension}) from the supplied base64 track`)
48
+ audioFilePath = path.join(tempDir, `audio.${analysis.extension}`)
49
+
50
+ await writeBase64ToFile(addBase64Header(audioTrack, analysis.extension), audioFilePath)
51
  }
52
 
53
  // Decode and concatenate base64 video tracks to temporary file
 
57
  // note: here we assume the input video is in mp4
58
 
59
  const analysis = extractBase64(audioTrack)
60
+ const videoFilePath = path.join(tempDir, `video${++i}.${analysis.extension}`)
61
 
62
  // console.log(`concatenateVideosWithAudio: writing down a video file (${analysis.extension}) from the supplied base64 track`)
63
 
64
+ await writeBase64ToFile(addBase64Header(track, analysis.extension), videoFilePath)
65
 
66
+ videoFilePaths.push(videoFilePath)
67
  }
68
 
69
  videoFilePaths = videoFilePaths.filter((video) => existsSync(video))
 
161
  try {
162
  if (asBase64) {
163
  try {
164
+ const outputBuffer = await readFile(finalOutputFilePath);
165
  const outputBase64 = addBase64Header(outputBuffer.toString("base64"), format)
166
  resolve(outputBase64);
167
  } catch (error) {
src/{core/ffmpeg/createVideoFromFrames.mts β†’ bug-in-bun/aitube_ffmpeg/concatenate/createVideoFromFrames.ts} RENAMED
@@ -5,7 +5,7 @@ import path from "node:path"
5
  import ffmpeg from "fluent-ffmpeg"
6
  import { v4 as uuidv4 } from "uuid"
7
 
8
- import { getMediaInfo } from "./getMediaInfo.mts"
9
 
10
  export async function createVideoFromFrames({
11
  inputFramesDirectory,
@@ -19,7 +19,7 @@ export async function createVideoFromFrames({
19
  // 3. grain has too much entropy and cannot be compressed, so it multiplies by 5 the size weight
20
  grainAmount = 0, // Optional parameter for film grain (eg. 10)
21
 
22
- inputVideoToUseAsAudio, // Optional parameter for audio input (need to be a mp4, but it can be a base64 data URI or a file path)
23
 
24
  debug = false,
25
 
@@ -42,7 +42,7 @@ export async function createVideoFromFrames({
42
 
43
 
44
  // Construct the input frame pattern
45
- const inputFramePattern = path.join(inputFramesDirectory, framesFilePattern);
46
 
47
 
48
  // Create a temporary working directory
 
5
  import ffmpeg from "fluent-ffmpeg"
6
  import { v4 as uuidv4 } from "uuid"
7
 
8
+ import { getMediaInfo } from "../analyze/getMediaInfo"
9
 
10
  export async function createVideoFromFrames({
11
  inputFramesDirectory,
 
19
  // 3. grain has too much entropy and cannot be compressed, so it multiplies by 5 the size weight
20
  grainAmount = 0, // Optional parameter for film grain (eg. 10)
21
 
22
+ inputVideoToUseAsAudio = "", // Optional parameter for audio input (need to be a mp4, but it can be a base64 data URI or a file path)
23
 
24
  debug = false,
25
 
 
42
 
43
 
44
  // Construct the input frame pattern
45
+ const inputFramePattern = path.join(inputFramesDirectory, framesFilePattern || "");
46
 
47
 
48
  // Create a temporary working directory
src/bug-in-bun/aitube_ffmpeg/concatenate/index.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ export { concatenateAudio } from "./concatenateAudio"
2
+ export type { ConcatenateAudioOutput } from "./concatenateAudio"
3
+ export { concatenateVideos } from "./concatenateVideos"
4
+ export { concatenateVideosAndMergeAudio } from "./concatenateVideosAndMergeAudio"
5
+ export { concatenateVideosWithAudio } from "./concatenateVideosWithAudio"
6
+ export { defaultExportFormat } from "./concatenateVideosWithAudio"
7
+ export type { SupportedExportFormat } from "./concatenateVideosWithAudio"
8
+ export { createVideoFromFrames } from "./createVideoFromFrames"
src/{core/ffmpeg/convertAudioToWav.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertAudioToWav.ts} RENAMED
@@ -1,8 +1,9 @@
1
- import { promises as fs } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import ffmpeg from "fluent-ffmpeg";
5
- import { Buffer } from "node:buffer";
 
6
 
7
  type ConvertAudioToWavParams = {
8
  input: string;
@@ -27,21 +28,21 @@ export async function convertAudioToWav({
27
 
28
  const inputBuffer = Buffer.from(matches[2], "base64");
29
  const inputFormat = matches[1]; // Either 'mp3' or 'wav'
30
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ffmpeg-input-"));
31
  inputAudioPath = path.join(tempDir, `temp.${inputFormat}`);
32
 
33
  // Write the base64 data to the temporary file
34
- await fs.writeFile(inputAudioPath, inputBuffer);
35
  } else {
36
  // Verify that the input file exists
37
- if (!(await fs.stat(inputAudioPath)).isFile()) {
38
  throw new Error(`Input audio file does not exist: ${inputAudioPath}`);
39
  }
40
  }
41
 
42
  // If no output path is provided, create a temporary file for the output
43
  if (!outputAudioPath) {
44
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ffmpeg-output-"));
45
  outputAudioPath = path.join(tempDir, `${path.parse(inputAudioPath).name}.wav`);
46
  }
47
 
@@ -54,7 +55,7 @@ export async function convertAudioToWav({
54
  .on("end", async () => {
55
  if (asBase64) {
56
  try {
57
- const audioBuffer = await fs.readFile(outputAudioPath);
58
  const audioBase64 = `data:audio/wav;base64,${audioBuffer.toString("base64")}`;
59
  resolve(audioBase64);
60
  } catch (error) {
 
1
+ import { mkdtemp, writeFile, readFile, stat } from "node:fs/promises"
2
+ import os from "node:os"
3
+ import path from "node:path"
4
+ import { Buffer } from "node:buffer"
5
+
6
+ import ffmpeg from "fluent-ffmpeg"
7
 
8
  type ConvertAudioToWavParams = {
9
  input: string;
 
28
 
29
  const inputBuffer = Buffer.from(matches[2], "base64");
30
  const inputFormat = matches[1]; // Either 'mp3' or 'wav'
31
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), "ffmpeg-input-"));
32
  inputAudioPath = path.join(tempDir, `temp.${inputFormat}`);
33
 
34
  // Write the base64 data to the temporary file
35
+ await writeFile(inputAudioPath, inputBuffer);
36
  } else {
37
  // Verify that the input file exists
38
+ if (!(await stat(inputAudioPath)).isFile()) {
39
  throw new Error(`Input audio file does not exist: ${inputAudioPath}`);
40
  }
41
  }
42
 
43
  // If no output path is provided, create a temporary file for the output
44
  if (!outputAudioPath) {
45
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), "ffmpeg-output-"));
46
  outputAudioPath = path.join(tempDir, `${path.parse(inputAudioPath).name}.wav`);
47
  }
48
 
 
55
  .on("end", async () => {
56
  if (asBase64) {
57
  try {
58
+ const audioBuffer = await readFile(outputAudioPath);
59
  const audioBase64 = `data:audio/wav;base64,${audioBuffer.toString("base64")}`;
60
  resolve(audioBase64);
61
  } catch (error) {
src/{core/ffmpeg/convertMp4ToMp3.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertMp4ToMp3.ts} RENAMED
@@ -1,10 +1,11 @@
1
 
2
- import { mkdtemp, stat, writeFile, readFile } from "node:fs/promises";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import ffmpeg from "fluent-ffmpeg";
6
- import { tmpdir } from "node:os";
7
- import { Buffer } from "node:buffer";
 
8
 
9
  export async function convertMp4ToMp3({
10
  input,
 
1
 
2
+ import { mkdtemp, stat, writeFile, readFile } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { tmpdir } from "node:os"
6
+ import { Buffer } from "node:buffer"
7
+
8
+ import ffmpeg from "fluent-ffmpeg"
9
 
10
  export async function convertMp4ToMp3({
11
  input,
src/{core/ffmpeg/convertMp4ToWebm.mts β†’ bug-in-bun/aitube_ffmpeg/convert/convertMp4ToWebm.ts} RENAMED
@@ -1,11 +1,10 @@
1
 
2
- import { mkdtemp, stat, writeFile, readFile } from "node:fs/promises";
3
- import path from "node:path";
 
 
4
 
5
- import { tmpdir } from "node:os";
6
- import { Buffer } from "node:buffer";
7
-
8
- import ffmpeg from "fluent-ffmpeg";
9
 
10
  export async function convertMp4ToWebm({
11
  input,
 
1
 
2
+ import { mkdtemp, stat, writeFile, readFile } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { tmpdir } from "node:os"
5
+ import { Buffer } from "node:buffer"
6
 
7
+ import ffmpeg from "fluent-ffmpeg"
 
 
 
8
 
9
  export async function convertMp4ToWebm({
10
  input,
src/bug-in-bun/aitube_ffmpeg/convert/index.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export { convertAudioToWav } from "./convertAudioToWav"
2
+ export { convertMp4ToMp3 } from "./convertMp4ToMp3"
3
+ export { convertMp4ToWebm } from "./convertMp4ToWebm"
src/bug-in-bun/aitube_ffmpeg/index.ts ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export {
2
+ getMediaInfo
3
+ } from "./analyze"
4
+
5
+ export {
6
+ concatenateAudio,
7
+ concatenateVideos,
8
+ concatenateVideosAndMergeAudio,
9
+ concatenateVideosWithAudio,
10
+ defaultExportFormat,
11
+ createVideoFromFrames
12
+ } from "./concatenate"
13
+
14
+ export type {
15
+ SupportedExportFormat,
16
+ ConcatenateAudioOutput
17
+ } from "./concatenate"
18
+
19
+ export {
20
+ convertAudioToWav,
21
+ convertMp4ToMp3,
22
+ convertMp4ToWebm,
23
+ } from "./convert"
24
+
25
+ export {
26
+ addImageToVideo,
27
+ addTextToVideo,
28
+ createTextOverlayImage,
29
+ getCssStyle,
30
+ htmlToBase64Png,
31
+ imageToVideoBase64,
32
+ } from "./overlay"
33
+
34
+ export {
35
+ cropBase64Video,
36
+ cropVideo,
37
+ scaleVideo,
38
+ } from "./transform"
src/{core/ffmpeg/addImageToVideo.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/addImageToVideo.ts} RENAMED
@@ -1,8 +1,9 @@
1
- import { promises as fs, existsSync } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import ffmpeg from "fluent-ffmpeg";
5
- import { v4 as uuidv4 } from "uuid";
 
6
 
7
  type AddImageToVideoParams = {
8
  inputVideoPath: string;
@@ -25,7 +26,7 @@ export async function addImageToVideo({
25
 
26
  // If no output path is provided, create a temporary file for output
27
  if (!outputVideoPath) {
28
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), uuidv4()));
29
  outputVideoPath = path.join(tempDir, `${uuidv4()}.mp4`);
30
  }
31
 
 
1
+ import { existsSync } from "node:fs"
2
+ import path from "node:path"
3
+
4
+ import ffmpeg from "fluent-ffmpeg"
5
+ import { v4 as uuidv4 } from "uuid"
6
+ import { getRandomDirectory } from "@aitube/io"
7
 
8
  type AddImageToVideoParams = {
9
  inputVideoPath: string;
 
26
 
27
  // If no output path is provided, create a temporary file for output
28
  if (!outputVideoPath) {
29
+ const tempDir = await getRandomDirectory()
30
  outputVideoPath = path.join(tempDir, `${uuidv4()}.mp4`);
31
  }
32
 
src/{core/ffmpeg/addTextToVideo.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/addTextToVideo.ts} RENAMED
@@ -1,6 +1,7 @@
1
- import { createTextOverlayImage } from "./createTextOverlayImage.mts"
2
- import { addImageToVideo } from "./addImageToVideo.mts"
3
- import { deleteFile } from "../files/deleteFile.mts"
 
4
 
5
  export async function addTextToVideo({
6
  inputVideoPath,
 
1
+ import { deleteFile } from "@aitube/io"
2
+
3
+ import { createTextOverlayImage } from "./createTextOverlayImage"
4
+ import { addImageToVideo } from "./addImageToVideo"
5
 
6
  export async function addTextToVideo({
7
  inputVideoPath,
src/{core/ffmpeg/createTextOverlayImage.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/createTextOverlayImage.ts} RENAMED
@@ -1,6 +1,6 @@
1
 
2
- import { TextOverlayFont, TextOverlayFontWeight, TextOverlayPosition, TextOverlayStyle, getCssStyle } from "../utils/getCssStyle.mts"
3
- import { htmlToBase64Png } from "../converters/htmlToBase64Png.mts"
4
 
5
  // generate a PNG overlay using HTML
6
  // most sizes are in percentage of the image height
 
1
 
2
+ import { TextOverlayFont, TextOverlayFontWeight, TextOverlayPosition, TextOverlayStyle, getCssStyle } from "./getCssStyle"
3
+ import { htmlToBase64Png } from "./htmlToBase64Png"
4
 
5
  // generate a PNG overlay using HTML
6
  // most sizes are in percentage of the image height
src/{core/utils/getCssStyle.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/getCssStyle.ts} RENAMED
File without changes
src/{core/converters/htmlToBase64Png.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/htmlToBase64Png.ts} RENAMED
@@ -7,7 +7,7 @@ import puppeteer from "puppeteer"
7
 
8
  export async function htmlToBase64Png({
9
  outputImagePath,
10
- html,
11
  width = 800,
12
  height = 600,
13
  }: {
@@ -53,6 +53,8 @@ export async function htmlToBase64Png({
53
 
54
  const content = await page.$("body")
55
 
 
 
56
  const buffer = await content.screenshot({
57
  path: outputImagePath,
58
  omitBackground: true,
 
7
 
8
  export async function htmlToBase64Png({
9
  outputImagePath,
10
+ html = "",
11
  width = 800,
12
  height = 600,
13
  }: {
 
53
 
54
  const content = await page.$("body")
55
 
56
+ if (!content) { throw new Error (`coudln't find body content`) }
57
+
58
  const buffer = await content.screenshot({
59
  path: outputImagePath,
60
  omitBackground: true,
src/{core/ffmpeg/imageToVideoBase64.mts β†’ bug-in-bun/aitube_ffmpeg/overlay/imageToVideoBase64.ts} RENAMED
@@ -1,8 +1,8 @@
1
- import { rm, mkdir, writeFile, readFile } from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import ffmpeg from "fluent-ffmpeg";
5
- import { getRandomDirectory } from "../files/getRandomDirectory.mts";
6
 
7
  /**
8
  * Converts an image in Base64 format to a video encoded in Base64.
@@ -82,13 +82,21 @@ export async function imageToVideoBase64({
82
  '-pix_fmt yuv420p'
83
  ])
84
 
85
- // Apply zoompan filter only if zoom rate is greater than 0
86
- if (zoomInRatePerSecond > 0) {
87
- const totalZoomFactor = 1 + (zoomInRatePerSecond / 100) * durationInSeconds;
88
- ffmpegCommand = ffmpegCommand.videoFilters(`zoompan=z='min(zoom+${zoomInRatePerSecond}/100,zoom*${totalZoomFactor})':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1`);
89
- }
90
-
 
 
 
 
 
91
  return ffmpegCommand
 
 
 
92
  .on('end', () => resolve())
93
  .on('error', (err) => reject(err))
94
  .save(outputFilePath);
 
1
+ import { rm, writeFile, readFile } from "node:fs/promises"
2
+ import path from "node:path"
3
+
4
+ import ffmpeg from "fluent-ffmpeg"
5
+ import { getRandomDirectory } from "@aitube/io"
6
 
7
  /**
8
  * Converts an image in Base64 format to a video encoded in Base64.
 
82
  '-pix_fmt yuv420p'
83
  ])
84
 
85
+ if (zoomInRatePerSecond > 0) {
86
+ const zoomIncreasePerSecond = zoomInRatePerSecond / 100;
87
+ const totalZoomFactor = 1 + (zoomIncreasePerSecond * durationInSeconds);
88
+ const framesTotal = durationInSeconds * fps;
89
+ const zoomPerFrame = zoomIncreasePerSecond / fps;
90
+
91
+ const zoomFormula = `if(lte(zoom\\,${totalZoomFactor}),zoom+${zoomPerFrame}\\,zoom)`;
92
+
93
+ ffmpegCommand = ffmpegCommand.videoFilters(`zoompan=z='${zoomFormula}':d=${framesTotal}:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)'`);
94
+ }
95
+
96
  return ffmpegCommand
97
+ .on('start', function(commandLine) {
98
+ console.log('imageToVideoBase64: Spawned Ffmpeg with command: ' + commandLine);
99
+ })
100
  .on('end', () => resolve())
101
  .on('error', (err) => reject(err))
102
  .save(outputFilePath);
src/bug-in-bun/aitube_ffmpeg/overlay/index.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export { addImageToVideo } from "./addImageToVideo"
2
+ export { addTextToVideo } from "./addTextToVideo"
3
+ export { createTextOverlayImage } from "./createTextOverlayImage"
4
+ export { getCssStyle } from "./getCssStyle"
5
+ export { htmlToBase64Png } from "./htmlToBase64Png"
6
+ export { imageToVideoBase64 } from "./imageToVideoBase64"
src/{core/ffmpeg/cropBase64Video.mts β†’ bug-in-bun/aitube_ffmpeg/transform/cropBase64Video.ts} RENAMED
@@ -1,8 +1,8 @@
1
- import { promises as fs } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
 
5
- import ffmpeg from "fluent-ffmpeg";
6
 
7
  export async function cropBase64Video({
8
  base64Video,
@@ -42,8 +42,8 @@ export async function cropBase64Video({
42
  }
43
 
44
  const { width: inWidth, height: inHeight } = videoStream;
45
- const x = Math.floor((inWidth - width) / 2);
46
- const y = Math.floor((inHeight - height) / 2);
47
 
48
  ffmpeg(inputVideoPath)
49
  .outputOptions([
 
1
+ import { promises as fs } from "node:fs"
2
+ import os from "node:os"
3
+ import path from "node:path"
4
 
5
+ import ffmpeg from "fluent-ffmpeg"
6
 
7
  export async function cropBase64Video({
8
  base64Video,
 
42
  }
43
 
44
  const { width: inWidth, height: inHeight } = videoStream;
45
+ const x = Math.floor(((inWidth || 0) - width) / 2);
46
+ const y = Math.floor(((inHeight || 0) - height) / 2);
47
 
48
  ffmpeg(inputVideoPath)
49
  .outputOptions([
src/{core/ffmpeg/cropVideo.mts β†’ bug-in-bun/aitube_ffmpeg/transform/cropVideo.ts} RENAMED
@@ -1,9 +1,8 @@
1
- import { promises as fs } from "node:fs";
2
- // import { writeFile, readFile } from 'node:fs/promises';
3
- import os from "node:os";
4
- import path from "node:path";
5
 
6
- import ffmpeg from "fluent-ffmpeg";
7
 
8
  export async function cropVideo({
9
  inputVideoPath,
@@ -42,9 +41,10 @@ export async function cropVideo({
42
  return;
43
  }
44
 
45
- const { width: inWidth, height: inHeight } = videoStream;
46
- const x = Math.floor((inWidth - width) / 2);
47
- const y = Math.floor((inHeight - height) / 2);
 
48
 
49
  ffmpeg(inputVideoPath)
50
  .outputOptions([
 
1
+ import { promises as fs } from "node:fs"
2
+ import os from "node:os"
3
+ import path from "node:path"
 
4
 
5
+ import ffmpeg from "fluent-ffmpeg"
6
 
7
  export async function cropVideo({
8
  inputVideoPath,
 
41
  return;
42
  }
43
 
44
+ const { width: inWidth, height: inHeight } = videoStream
45
+
46
+ const x = Math.floor(((inWidth || 0) - width) / 2)
47
+ const y = Math.floor(((inHeight || 0) - height) / 2)
48
 
49
  ffmpeg(inputVideoPath)
50
  .outputOptions([
src/bug-in-bun/aitube_ffmpeg/transform/index.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export { cropBase64Video } from "./cropBase64Video"
2
+ export { cropVideo } from "./cropVideo"
3
+ export { scaleVideo } from "./scaleVideo"
src/{core/ffmpeg/scaleVideo.mts β†’ bug-in-bun/aitube_ffmpeg/transform/scaleVideo.ts} RENAMED
@@ -1,10 +1,9 @@
1
- import fs from 'node:fs/promises';
2
- import { writeFile, readFile } from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
 
6
- import { v4 as uuidv4 } from "uuid";
7
- import ffmpeg from 'fluent-ffmpeg';
8
 
9
  export type ScaleVideoParams = {
10
  input: string;
@@ -25,8 +24,6 @@ export type ScaleVideoParams = {
25
  * Upon completion, the temporary output file is read into a buffer, converted to a base64 string with the correct prefix, and then cleaned up by removing temporary files.
26
  * To call this function with desired input and height, you'd use it similarly to the provided convertMp4ToMp3 function example, being mindful that input must be a file path or properly-formatted base64 string and height is a number representing the new height of the video.
27
  *
28
- * Enter your message...
29
- *
30
  * @param param0
31
  * @returns
32
  */
@@ -36,7 +33,7 @@ export async function scaleVideo({
36
  asBase64 = false,
37
  debug = false
38
  }: ScaleVideoParams): Promise<string> {
39
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ffmpeg-"));
40
  const tempOutPath = path.join(tempDir, `${uuidv4()}.mp4`);
41
 
42
  let inputPath;
@@ -82,7 +79,7 @@ export async function scaleVideo({
82
  reject(new Error(`Error loading the video file: ${error}`));
83
  } finally {
84
  // Clean up temporary files
85
- await fs.rm(tempDir, { recursive: true });
86
  }
87
  })
88
  .save(tempOutPath);
 
1
+ import { rm, mkdtemp, writeFile, readFile } from 'node:fs/promises'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
 
4
 
5
+ import { v4 as uuidv4 } from "uuid"
6
+ import ffmpeg from 'fluent-ffmpeg'
7
 
8
  export type ScaleVideoParams = {
9
  input: string;
 
24
  * Upon completion, the temporary output file is read into a buffer, converted to a base64 string with the correct prefix, and then cleaned up by removing temporary files.
25
  * To call this function with desired input and height, you'd use it similarly to the provided convertMp4ToMp3 function example, being mindful that input must be a file path or properly-formatted base64 string and height is a number representing the new height of the video.
26
  *
 
 
27
  * @param param0
28
  * @returns
29
  */
 
33
  asBase64 = false,
34
  debug = false
35
  }: ScaleVideoParams): Promise<string> {
36
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), "ffmpeg-"));
37
  const tempOutPath = path.join(tempDir, `${uuidv4()}.mp4`);
38
 
39
  let inputPath;
 
79
  reject(new Error(`Error loading the video file: ${error}`));
80
  } finally {
81
  // Clean up temporary files
82
+ await rm(tempDir, { recursive: true });
83
  }
84
  })
85
  .save(tempOutPath);
src/core/base64/addBase64.mts DELETED
@@ -1,51 +0,0 @@
1
- export function addBase64Header(
2
- image?: string,
3
- format?:
4
- | "jpeg" | "jpg" | "png" | "webp" | "heic"
5
- | "mp3" | "wav"
6
- | "mp4" | "webm"
7
- | string
8
- ) {
9
-
10
- if (!image || typeof image !== "string" || image.length < 60) {
11
- return ""
12
- }
13
-
14
- const ext = (`${format || ""}`.split(".").pop() || "").toLowerCase().trim()
15
-
16
- let mime = ""
17
- if (
18
- ext === "jpeg" ||
19
- ext === "jpg") {
20
- mime = "image/jpeg"
21
- } else if (
22
- ext === "webp"
23
- ) {
24
- mime = "image/webp"
25
- } else if (
26
- ext === "png") {
27
- mime = "image/png"
28
- } else if (ext === "heic") {
29
- mime = "image/heic"
30
- } else if (ext === "mp3") {
31
- mime = "audio/mp3"
32
- } else if (ext === "mp4") {
33
- mime = "video/mp4"
34
- } else if (ext === "webm") {
35
- mime = "video/webm"
36
- } else if (ext === "wav") {
37
- mime = "audio/wav"
38
- } else {
39
- throw new Error(`addBase64Header failed (unsupported format: ${format})`)
40
- }
41
-
42
- if (image.startsWith('data:')) {
43
- if (image.startsWith(`data:${mime};base64,`)) {
44
- return image
45
- } else {
46
- throw new Error(`addBase64Header failed (input string is NOT a ${mime} image)`)
47
- }
48
- } else {
49
- return `data:${mime};base64,${image}`
50
- }
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/base64/dataUriToBlob.mts DELETED
@@ -1,15 +0,0 @@
1
-
2
- export function dataUriToBlob(dataURI = "", defaultContentType = ""): Blob {
3
- dataURI = dataURI.replace(/^data:/, '');
4
-
5
- const type = dataURI.match(/(?:image|application|video|audio|text)\/[^;]+/)?.[0] || defaultContentType;
6
- const base64 = dataURI.replace(/^[^,]+,/, '');
7
- const arrayBuffer = new ArrayBuffer(base64.length);
8
- const typedArray = new Uint8Array(arrayBuffer);
9
-
10
- for (let i = 0; i < base64.length; i++) {
11
- typedArray[i] = base64.charCodeAt(i);
12
- }
13
-
14
- return new Blob([arrayBuffer], { type });
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/base64/extractBase64.mts DELETED
@@ -1,39 +0,0 @@
1
- /**
2
- * break a base64 string into sub-components
3
- */
4
- export function extractBase64(base64: string = ""): {
5
-
6
- // file format eg. video/mp4 text/html audio/wave
7
- mimetype: string;
8
-
9
- // file extension eg. .mp4 .html .wav
10
- extension: string;
11
-
12
- data: string;
13
- buffer: Buffer;
14
- blob: Blob;
15
- } {
16
- // console.log(`extractBase64(${base64.slice(0, 120)})`)
17
- // Regular expression to extract the MIME type and the base64 data
18
- const matches = base64.match(/^data:([A-Za-z-+0-9/]+);base64,(.+)$/)
19
-
20
- if (!matches || matches.length !== 3) {
21
- throw new Error("Invalid base64 string")
22
- }
23
-
24
- const mimetype = matches[1] || ""
25
- const data = matches[2] || ""
26
- const buffer = Buffer.from(data, "base64")
27
- const blob = new Blob([buffer])
28
-
29
- // this should be enough for most media formats (jpeg, png, webp, mp4)
30
- const extension = mimetype.split("/").pop() || ""
31
-
32
- return {
33
- mimetype,
34
- extension,
35
- data,
36
- buffer,
37
- blob,
38
- }
39
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/converters/blobToWebp.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function blobToWebp(blob: Blob) {
4
- return addBase64Header(Buffer.from(await blob.text()).toString('base64'), "webp")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToJpeg.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToJpeg(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "jpeg")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToMp3.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToMp3(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "mp3")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToMp4.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToMp4(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "mp4")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToPng.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToPng(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "png")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToWav.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToWav(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "wav")
5
- }
 
 
 
 
 
 
src/core/converters/bufferToWebp.mts DELETED
@@ -1,5 +0,0 @@
1
- import { addBase64Header } from "../base64/addBase64.mts";
2
-
3
- export async function bufferToWebp(buffer: Buffer) {
4
- return addBase64Header(buffer.toString('base64'), "webp")
5
- }
 
 
 
 
 
 
src/core/converters/convertImageTo.mts DELETED
@@ -1,31 +0,0 @@
1
- import { convertImageToJpeg } from "./convertImageToJpeg.mts"
2
- import { convertImageToPng } from "./convertImageToPng.mts"
3
- import { convertImageToWebp } from "./convertImageToWebp.mts"
4
- import { ImageFileExt } from "./imageFormats.mts"
5
-
6
- /**
7
- * Convert an image to one of the supported file formats
8
- *
9
- * @param imgBase64
10
- * @param outputFormat
11
- * @returns
12
- */
13
- export async function convertImageTo(imgBase64: string = "", outputFormat: ImageFileExt): Promise<string> {
14
- const format = outputFormat.trim().toLowerCase() as ImageFileExt
15
- if (!["jpeg", "jpg", "png", "webp"].includes(format)) {
16
- throw new Error(`unsupported file format "${format}"`)
17
- }
18
-
19
- const isJpeg = format === "jpg" || format === "jpeg"
20
-
21
-
22
- if (isJpeg) {
23
- return convertImageToJpeg(imgBase64)
24
- }
25
-
26
- if (format === "webp") {
27
- return convertImageToWebp(imgBase64)
28
- }
29
-
30
- return convertImageToPng(imgBase64)
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/converters/convertImageToJpeg.mts DELETED
@@ -1,27 +0,0 @@
1
- import sharp from "sharp"
2
-
3
- export async function convertImageToJpeg(imgBase64: string = "", quality: number = 92): Promise<string> {
4
-
5
- const base64WithoutHeader = imgBase64.split(";base64,")[1] || ""
6
-
7
- if (!base64WithoutHeader) {
8
- const slice = `${imgBase64 || ""}`.slice(0, 50)
9
- throw new Error(`couldn't process input image "${slice}..."`)
10
- }
11
-
12
- // Convert base64 to buffer
13
- const tmpBuffer = Buffer.from(base64WithoutHeader, 'base64')
14
-
15
- // Resize the buffer to the target size
16
- const newBuffer = await sharp(tmpBuffer)
17
- .jpeg({
18
- quality,
19
- // we don't use progressive: true because we pre-load images anyway
20
- })
21
- .toBuffer()
22
-
23
- // Convert the buffer back to base64
24
- const newImageBase64 = newBuffer.toString('base64')
25
-
26
- return `data:image/jpeg;base64,${newImageBase64}`
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/converters/convertImageToOriginal.mts DELETED
@@ -1,6 +0,0 @@
1
-
2
- // you are reading it right: this function does.. nothing!
3
- // it is a NOOP conversion function
4
- export async function convertImageToOriginal(imgBase64: string = ""): Promise<string> {
5
- return imgBase64
6
- }
 
 
 
 
 
 
 
src/core/converters/convertImageToPng.mts DELETED
@@ -1,23 +0,0 @@
1
- import sharp from "sharp"
2
-
3
- export async function convertImageToPng(imgBase64: string = ""): Promise<string> {
4
-
5
- const base64WithoutHeader = imgBase64.split(";base64,")[1] || ""
6
-
7
- if (!base64WithoutHeader) {
8
- const slice = `${imgBase64 || ""}`.slice(0, 50)
9
- throw new Error(`couldn't process input image "${slice}..."`)
10
- }
11
-
12
- // Convert base64 to buffer
13
- const tmpBuffer = Buffer.from(base64WithoutHeader, 'base64')
14
-
15
- const newBuffer = await sharp(tmpBuffer)
16
- .png()
17
- .toBuffer()
18
-
19
- // Convert the buffer back to base64
20
- const newImageBase64 = newBuffer.toString('base64')
21
-
22
- return `data:image/png;base64,${newImageBase64}`
23
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/converters/convertImageToWebp.mts DELETED
@@ -1,41 +0,0 @@
1
- import sharp from "sharp"
2
-
3
- export async function convertImageToWebp(imgBase64: string = ""): Promise<string> {
4
-
5
- const base64WithoutHeader = imgBase64.split(";base64,")[1] || ""
6
-
7
- if (!base64WithoutHeader) {
8
- const slice = `${imgBase64 || ""}`.slice(0, 50)
9
- throw new Error(`couldn't process input image "${slice}..."`)
10
- }
11
-
12
- // Convert base64 to buffer
13
- const tmpBuffer = Buffer.from(base64WithoutHeader, 'base64')
14
-
15
- // Resize the buffer to the target size
16
- const newBuffer = await sharp(tmpBuffer)
17
- .webp({
18
- // for options please see https://sharp.pixelplumbing.com/api-output#webp
19
-
20
- // preset: "photo",
21
-
22
- // effort: 3,
23
-
24
- // for a PNG-like quality
25
- // lossless: true,
26
-
27
- // by default it is quality 80
28
- quality: 80,
29
-
30
- // nearLossless: true,
31
-
32
- // use high quality chroma subsampling
33
- smartSubsample: true,
34
- })
35
- .toBuffer()
36
-
37
- // Convert the buffer back to base64
38
- const newImageBase64 = newBuffer.toString('base64')
39
-
40
- return `data:image/webp;base64,${newImageBase64}`
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/converters/imageFormats.mts DELETED
@@ -1 +0,0 @@
1
- export type ImageFileExt = "png" | "jpeg" | "jpg" | "webp"
 
 
src/core/exporters/{clapWithStoryboardsToVideoFile.mts β†’ clapWithStoryboardsToVideoFile.ts} RENAMED
@@ -1,7 +1,7 @@
1
  import { ClapProject, ClapSegment } from "@aitube/clap"
 
2
 
3
- import { getRandomDirectory } from "../files/getRandomDirectory.mts"
4
- import { storyboardSegmentToVideoFile } from "./storyboardSegmentToVideoFile.mts"
5
 
6
  export async function clapWithStoryboardsToVideoFile({
7
  clap,
 
1
  import { ClapProject, ClapSegment } from "@aitube/clap"
2
+ import { getRandomDirectory } from "@aitube/io"
3
 
4
+ import { storyboardSegmentToVideoFile } from "./storyboardSegmentToVideoFile"
 
5
 
6
  export async function clapWithStoryboardsToVideoFile({
7
  clap,
src/core/exporters/{clapWithVideosToVideoFile.mts β†’ clapWithVideosToVideoFile.ts} RENAMED
@@ -1,8 +1,7 @@
1
  import { ClapProject, ClapSegment } from "@aitube/clap"
 
2
 
3
- import { getRandomDirectory } from "../files/getRandomDirectory.mts"
4
- import { videoSegmentToVideoFile } from "./videoSegmentToVideoFile.mts"
5
-
6
 
7
  export async function clapWithVideosToVideoFile({
8
  clap,
 
1
  import { ClapProject, ClapSegment } from "@aitube/clap"
2
+ import { getRandomDirectory } from "@aitube/io"
3
 
4
+ import { videoSegmentToVideoFile } from "./videoSegmentToVideoFile"
 
 
5
 
6
  export async function clapWithVideosToVideoFile({
7
  clap,
src/core/exporters/{storyboardSegmentToVideoFile.mts β†’ storyboardSegmentToVideoFile.ts} RENAMED
@@ -1,14 +1,12 @@
1
  import { join } from "node:path"
2
 
3
  import { ClapProject, ClapSegment } from "@aitube/clap"
 
 
 
 
4
 
5
- import { concatenateVideosWithAudio } from "../ffmpeg/concatenateVideosWithAudio.mts"
6
- import { writeBase64ToFile } from "../files/writeBase64ToFile.mts"
7
- import { addTextToVideo } from "../ffmpeg/addTextToVideo.mts"
8
- import { startOfSegment1IsWithinSegment2 } from "../utils/startOfSegment1IsWithinSegment2.mts"
9
- import { deleteFile } from "../files/deleteFile.mts"
10
- import { extractBase64 } from "../base64/extractBase64.mts"
11
- import { imageToVideoBase64 } from "../ffmpeg/imageToVideoBase64.mts"
12
 
13
  export async function storyboardSegmentToVideoFile({
14
  clap,
 
1
  import { join } from "node:path"
2
 
3
  import { ClapProject, ClapSegment } from "@aitube/clap"
4
+ import { extractBase64 } from "@aitube/encoders"
5
+ import { deleteFile, writeBase64ToFile } from "@aitube/io"
6
+ //import { addTextToVideo, concatenateVideosWithAudio, imageToVideoBase64 } from "@aitube/ffmpeg"
7
+ import { addTextToVideo, concatenateVideosWithAudio, imageToVideoBase64 } from "../../bug-in-bun/aitube_ffmpeg"
8
 
9
+ import { startOfSegment1IsWithinSegment2 } from "../utils/startOfSegment1IsWithinSegment2"
 
 
 
 
 
 
10
 
11
  export async function storyboardSegmentToVideoFile({
12
  clap,
src/core/exporters/{videoSegmentToVideoFile.mts β†’ videoSegmentToVideoFile.ts} RENAMED
@@ -1,14 +1,12 @@
1
  import { join } from "node:path"
2
 
3
  import { ClapProject, ClapSegment } from "@aitube/clap"
 
 
 
 
4
 
5
- import { concatenateVideosWithAudio } from "../ffmpeg/concatenateVideosWithAudio.mts"
6
- import { writeBase64ToFile } from "../files/writeBase64ToFile.mts"
7
- import { addTextToVideo } from "../ffmpeg/addTextToVideo.mts"
8
- import { startOfSegment1IsWithinSegment2 } from "../utils/startOfSegment1IsWithinSegment2.mts"
9
- import { deleteFile } from "../files/deleteFile.mts"
10
- import { extractBase64 } from "../base64/extractBase64.mts"
11
-
12
 
13
  export async function videoSegmentToVideoFile({
14
  clap,
 
1
  import { join } from "node:path"
2
 
3
  import { ClapProject, ClapSegment } from "@aitube/clap"
4
+ import { extractBase64 } from "@aitube/encoders"
5
+ import { deleteFile, writeBase64ToFile } from "@aitube/io"
6
+ // import { addTextToVideo, concatenateVideosWithAudio } from "@aitube/ffmpeg"
7
+ import { addTextToVideo, concatenateVideosWithAudio } from "../../bug-in-bun/aitube_ffmpeg"
8
 
9
+ import { startOfSegment1IsWithinSegment2 } from "../utils/startOfSegment1IsWithinSegment2"
 
 
 
 
 
 
10
 
11
  export async function videoSegmentToVideoFile({
12
  clap,
src/core/files/deleteFile.mts DELETED
@@ -1,14 +0,0 @@
1
- import { unlink, rm } from "node:fs/promises"
2
-
3
- export async function deleteFile(filePath: string, debug?: boolean): Promise<boolean> {
4
- try {
5
- await rm(filePath, { recursive: true, force: true })
6
- // await unlink(filePath)
7
- return true
8
- } catch (err) {
9
- if (debug) {
10
- console.error(`failed to unlink file at ${filePath}: ${err}`)
11
- }
12
- }
13
- return false
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/core/files/deleteFileWithName.mts DELETED
@@ -1,12 +0,0 @@
1
- import { promises as fs } from "node:fs"
2
- import path from "node:path"
3
- import { deleteFile } from "./deleteFile.mts"
4
-
5
- export const deleteFilesWithName = async (dir: string, name: string, debug?: boolean) => {
6
- console.log(`deleteFilesWithName(${dir}, ${name})`)
7
- for (const file of await fs.readdir(dir)) {
8
- if (file.includes(name)) {
9
- await deleteFile(path.join(dir, file))
10
- }
11
- }
12
- }