-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
122 lines (100 loc) · 3.45 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Video Editing
let editly = import('editly');
const extractAudio = require('ffmpeg-extract-audio');
const { getVideoDurationInSeconds } = require('get-video-duration');
// Tiktok Splash Detection
const extractFrames = require('ffmpeg-extract-frames');
const pixels = require('image-pixels');
// File Management
const fs = require('fs-extra');
// Editing Spec
let TIKTOK_AUDIO_DESYNC = 1 / 30;
const TIKTOK_AVAILABLE_DESYNCS = { SPLASH: 1 / 30, NOSPLASH: 4 / 30 };
const TIKTOK_WATERMARK_DURATION = 4;
const TIKTOK_SPLASH_RGB = [14, 14, 26];
const EDIT_SPEC = require('./resource/editspec.json');
function updateEditSpec(SONG_ID, duration) {
if (TIKTOK_AUDIO_DESYNC === TIKTOK_AVAILABLE_DESYNCS.SPLASH) duration -= TIKTOK_WATERMARK_DURATION;
EDIT_SPEC.clips[0].layers[0].path = `./input/${SONG_ID}.mp4`;
EDIT_SPEC.clips[0].layers[0].cutTo = duration;
EDIT_SPEC.outPath = `output/${SONG_ID}.mp4`;
EDIT_SPEC.audioTracks[0].start = TIKTOK_AUDIO_DESYNC; // Set the desync offset
}
async function generateVideo(SONG_ID, duration) {
updateEditSpec(SONG_ID, duration);
await editly(EDIT_SPEC);
}
async function getAudioFile(SONG_ID) {
await extractAudio({
input: `input/${SONG_ID}.mp4`,
output: 'resource/current.mp3'
});
}
async function checkForTiktokSplash(SONG_ID, duration) {
await extractFrames({
input: `./input/${SONG_ID}.mp4`,
output: './resource/frame.jpg',
offsets: [
(duration - TIKTOK_WATERMARK_DURATION / 2) * 1000
]
});
// Load the pixel data of the saved frame
const { data, width, height } = await pixels('./resource/frame.jpg');
let splashPixelCount = 0;
let notSplashPixelCount = 0;
for (let a = 0; a < data.length; a += 4) {
if (data[a] === TIKTOK_SPLASH_RGB[0] && data[a + 1] === TIKTOK_SPLASH_RGB[1] && data[a + 2] === TIKTOK_SPLASH_RGB[2]) splashPixelCount++;
else notSplashPixelCount++;
}
// If the tiktok splash RGBA makes up more than 85% of the screen we can safely assume it's the tiktok watermark splash screen
if (0.85 < splashPixelCount / (data.length / 4)) return true;
else return false;
}
function cleanup() {
fs.rmSync('resource/current.mp3');
fs.rmSync('resource/frame.jpg');
}
async function processSong(SONG_ID) {
await new Promise(async resolve => {
await getAudioFile(SONG_ID);
// From a local path...
getVideoDurationInSeconds(`./input/${SONG_ID}.mp4`).then(async (duration) => {
// Check if we have a splash screen at the end of the video
const hasSplashScreenToRemove = await checkForTiktokSplash(SONG_ID, duration);
if (hasSplashScreenToRemove) {
console.log(`SPLASH: ${SONG_ID}`);
TIKTOK_AUDIO_DESYNC = TIKTOK_AVAILABLE_DESYNCS.SPLASH;
} else {
console.log(`NO: ${SONG_ID}`);
TIKTOK_AUDIO_DESYNC = TIKTOK_AVAILABLE_DESYNCS.NOSPLASH;
}
// Generate the video
await generateVideo(SONG_ID, duration);
return resolve();
});
});
}
async function init() {
editly = (await editly).default;
EDIT_SPEC.clips = [{ layers: [{ type: 'video', path: `./input/PLACEHOLDER.mp4`, resizeMode: 'cover' }] }];
}
async function run() {
await init();
fs.ensureDirSync('output');
const files = fs.readdirSync('input');
for (const file of files) {
if (file.indexOf(".mp4") != -1) {
const SONG_ID = file.split('.mp4')[0];
// Process song if output file doesn't exist
try {
const existingStats = fs.statSync(`output/${SONG_ID}.mp4`);
} catch (err) {
console.log(`PROCESSING: ${SONG_ID}`);
await processSong(SONG_ID);
cleanup();
}
}
}
console.log("DONE!");
}
run();