-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdataToVideo.js
218 lines (197 loc) · 6.68 KB
/
dataToVideo.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env node
"use strict";
const fs = require("fs");
const fsExtra = require("fs-extra");
const path = require("path");
const { spawn } = require("child_process");
const prompt = require("prompt-sync")();
const ProgressManager = require("./progressManager");
// Konfigurace videa
const videoWidth = 1920;
const videoHeight = 1080;
const frameSize = videoWidth * videoHeight * 3;
// Kódovací barevná mapa (stejná, jakou používáme pro převod nibble na pixel)
const colorMap = {
"0": { r: 255, g: 255, b: 255 },
"1": { r: 0, g: 0, b: 0 },
"2": { r: 255, g: 0, b: 0 },
"3": { r: 0, g: 255, b: 0 },
"4": { r: 0, g: 0, b: 255 },
"5": { r: 255, g: 255, b: 0 },
"6": { r: 0, g: 255, b: 255 },
"7": { r: 255, g: 0, b: 255 },
"8": { r: 128, g: 0, b: 0 },
"9": { r: 0, g: 128, b: 0 },
"A": { r: 0, g: 0, b: 128 },
"B": { r: 255, g: 165, b: 0 },
"C": { r: 75, g: 0, b: 130 },
"D": { r: 173, g: 255, b: 47 },
"E": { r: 255, g: 20, b: 147 },
"F": { r: 192, g: 192, b: 192 }
};
// Pomocná funkce: převod řetězce na nibble – každý znak se převede na dvě 4-bitové hodnoty
function stringToNibbles(str) {
let nibbles = [];
for (const char of str) {
const binary = char.charCodeAt(0).toString(2).padStart(8, "0");
nibbles.push(binary.substring(0, 4));
nibbles.push(binary.substring(4, 8));
}
return nibbles;
}
// Vrací barvu podle nibble – nibble se nejprve převede z binárního řetězce na hexadecimální znak.
function getColorFromNibble(nibble) {
if (!colorMap[nibble]) {
console.warn(`Invalid nibble: ${nibble}`);
return { r: 0, g: 0, b: 0 };
}
return colorMap[nibble];
}
// Funkce vloží do pixelového proudu informaci o názvu souboru pomocí markerů
function pushFilename(filename, appendPixel) {
// Marker začátku – použijeme pixel {128,128,128}
appendPixel({ r: 128, g: 128, b: 128 });
const nibbles = stringToNibbles(filename);
for (const nib of nibbles) {
const hexDigit = parseInt(nib, 2).toString(16).toUpperCase();
appendPixel(getColorFromNibble(hexDigit));
}
// Marker konce názvu
appendPixel({ r: 128, g: 128, b: 128 });
}
// Globální buffer a offset pro aktuální snímek
let currentFrameBuffer = Buffer.alloc(frameSize);
let currentOffset = 0;
// Funkce, která přidá pixel do aktuálního snímkového bufferu a v případě, že je snímek plný, ho ihned odešle do ffmpeg.
function appendPixel(pixel, ffmpegStdin) {
if (currentOffset + 3 > frameSize) {
flushFrameBuffer(ffmpegStdin);
}
currentFrameBuffer[currentOffset] = pixel.r;
currentFrameBuffer[currentOffset + 1] = pixel.g;
currentFrameBuffer[currentOffset + 2] = pixel.b;
currentOffset += 3;
if (currentOffset === frameSize) {
ffmpegStdin.write(currentFrameBuffer);
currentFrameBuffer = Buffer.alloc(frameSize);
currentOffset = 0;
}
}
// Pokud zbyde částečný snímek, doplní ho černou barvou a odešle.
function flushFrameBuffer(ffmpegStdin) {
if (currentOffset > 0) {
currentFrameBuffer.fill(0, currentOffset);
ffmpegStdin.write(currentFrameBuffer);
currentFrameBuffer = Buffer.alloc(frameSize);
currentOffset = 0;
}
}
// Převod datového chunku na pole pixelů (každý byte se převede na dvě barvy podle hexadecimální hodnoty)
function processDataChunk(chunk) {
let pixels = [];
for (let i = 0; i < chunk.length; i++) {
const byte = chunk[i];
const highNibble = (byte >> 4).toString(16).toUpperCase();
const lowNibble = (byte & 0x0F).toString(16).toUpperCase();
pixels.push(getColorFromNibble(highNibble));
pixels.push(getColorFromNibble(lowNibble));
}
return pixels;
}
// Získá všechny soubory z adresáře (rekurzivně)
function getAllFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(file => {
const fullPath = path.join(dir, file);
if (fs.statSync(fullPath).isDirectory()) {
getAllFiles(fullPath, fileList);
} else {
fileList.push(fullPath);
}
});
return fileList;
}
async function main() {
const inputPath = process.argv[2] || prompt("Define input file/directory: ");
const outputVideoPath = path.join(__dirname, "./../output_video/out.mkv");
let files = [];
if (fs.statSync(inputPath).isDirectory()) {
files = getAllFiles(inputPath);
} else {
files.push(inputPath);
}
console.log(`Budu zpracovávat ${files.length} souborů.`);
// Spuštění ffmpeg pro kódování raw videa do lossless videa pomocí ffv1
const ffmpegArgs = [
"-y",
"-f",
"rawvideo",
"-pixel_format",
"rgb24",
"-video_size",
`${videoWidth}x${videoHeight}`,
"-framerate",
"30",
"-i",
"-",
"-c:v",
"ffv1",
"-preset",
"ultrafast",
outputVideoPath,
];
const ffmpeg = spawn("ffmpeg", ffmpegArgs);
ffmpeg.on("error", (err) => {
console.error("Chyba ffmpeg:", err);
process.exit(1);
});
ffmpeg.stderr.on("data", (data) => {
// Volitelně můžeš logovat progres; zatím zakomentováno
// console.error(data.toString());
});
// Použijeme číslovaný cyklus, abychom věděli, jestli zpracováváme poslední soubor
for (let i = 0; i < files.length; i++) {
const file = files[i];
console.log(`Zpracovávám soubor: ${file}`);
const fileSize = fs.statSync(file).size;
const progressManager = new ProgressManager(fileSize, path.basename(file));
// Vložíme název souboru jako marker
pushFilename(file, (pixel) => {
appendPixel(pixel, ffmpeg.stdin);
});
await new Promise((resolve, reject) => {
const stream = fs.createReadStream(file, { highWaterMark: 1024 * 1024 });
stream.on("data", (chunk) => {
progressManager.update(chunk.length);
const pixels = processDataChunk(chunk);
for (const pixel of pixels) {
appendPixel(pixel, ffmpeg.stdin);
}
});
stream.on("end", () => {
// Vložíme terminátor – marker D: pixel s hodnotami {128, 0, 128}
appendPixel({ r: 128, g: 0, b: 128 }, ffmpeg.stdin);
// Pokud je to poslední soubor, doplníme aktuální snímek (neúplný blok) černými pixely a flushneme ho.
if (i === files.length - 1) {
flushFrameBuffer(ffmpeg.stdin);
}
console.log(`Dokončeno: ${file}`);
resolve();
});
stream.on("error", (err) => reject(err));
});
}
// Po zpracování všech souborů flushneme zůstalé pixely a ukončíme stdin ffmpeg
flushFrameBuffer(ffmpeg.stdin);
ffmpeg.stdin.end();
ffmpeg.on("close", (code) => {
if (code === 0) {
console.log("Video bylo úspěšně vytvořeno!");
} else {
console.error(`FFmpeg skončil s kódem ${code}`);
}
});
}
main().catch((err) => {
console.error("Chyba v dataToVideo.js:", err);
});