1
+ #!/usr/bin/env node
2
+ "use strict" ;
3
+
4
+ const fs = require ( "fs" ) ;
5
+ const fsExtra = require ( "fs-extra" ) ;
6
+ const path = require ( "path" ) ;
7
+ const { spawn } = require ( "child_process" ) ;
8
+ const prompt = require ( "prompt-sync" ) ( ) ;
9
+ const ProgressManager = require ( "./progressManager" ) ;
10
+
11
+ // Konfigurace videa
12
+ const videoWidth = 1920 ;
13
+ const videoHeight = 1080 ;
14
+ const frameSize = videoWidth * videoHeight * 3 ;
15
+
16
+ // Kódovací barevná mapa (stejná, jakou používáme pro převod nibble na pixel)
17
+ const colorMap = {
18
+ "0" : { r : 255 , g : 255 , b : 255 } ,
19
+ "1" : { r : 0 , g : 0 , b : 0 } ,
20
+ "2" : { r : 255 , g : 0 , b : 0 } ,
21
+ "3" : { r : 0 , g : 255 , b : 0 } ,
22
+ "4" : { r : 0 , g : 0 , b : 255 } ,
23
+ "5" : { r : 255 , g : 255 , b : 0 } ,
24
+ "6" : { r : 0 , g : 255 , b : 255 } ,
25
+ "7" : { r : 255 , g : 0 , b : 255 } ,
26
+ "8" : { r : 128 , g : 0 , b : 0 } ,
27
+ "9" : { r : 0 , g : 128 , b : 0 } ,
28
+ "A" : { r : 0 , g : 0 , b : 128 } ,
29
+ "B" : { r : 255 , g : 165 , b : 0 } ,
30
+ "C" : { r : 75 , g : 0 , b : 130 } ,
31
+ "D" : { r : 173 , g : 255 , b : 47 } ,
32
+ "E" : { r : 255 , g : 20 , b : 147 } ,
33
+ "F" : { r : 192 , g : 192 , b : 192 }
34
+ } ;
35
+
36
+ // Pomocná funkce: převod řetězce na nibble – každý znak se převede na dvě 4-bitové hodnoty
37
+ function stringToNibbles ( str ) {
38
+ let nibbles = [ ] ;
39
+ for ( const char of str ) {
40
+ const binary = char . charCodeAt ( 0 ) . toString ( 2 ) . padStart ( 8 , "0" ) ;
41
+ nibbles . push ( binary . substring ( 0 , 4 ) ) ;
42
+ nibbles . push ( binary . substring ( 4 , 8 ) ) ;
43
+ }
44
+ return nibbles ;
45
+ }
46
+
47
+ // Vrací barvu podle nibble – nibble se nejprve převede z binárního řetězce na hexadecimální znak.
48
+ function getColorFromNibble ( nibble ) {
49
+ if ( ! colorMap [ nibble ] ) {
50
+ console . warn ( `Invalid nibble: ${ nibble } ` ) ;
51
+ return { r : 0 , g : 0 , b : 0 } ;
52
+ }
53
+ return colorMap [ nibble ] ;
54
+ }
55
+
56
+ // Funkce vloží do pixelového proudu informaci o názvu souboru pomocí markerů
57
+ function pushFilename ( filename , appendPixel ) {
58
+ // Marker začátku – použijeme pixel {128,128,128}
59
+ appendPixel ( { r : 128 , g : 128 , b : 128 } ) ;
60
+ const nibbles = stringToNibbles ( filename ) ;
61
+ for ( const nib of nibbles ) {
62
+ const hexDigit = parseInt ( nib , 2 ) . toString ( 16 ) . toUpperCase ( ) ;
63
+ appendPixel ( getColorFromNibble ( hexDigit ) ) ;
64
+ }
65
+ // Marker konce názvu
66
+ appendPixel ( { r : 128 , g : 128 , b : 128 } ) ;
67
+ }
68
+
69
+ // Globální buffer a offset pro aktuální snímek
70
+ let currentFrameBuffer = Buffer . alloc ( frameSize ) ;
71
+ let currentOffset = 0 ;
72
+
73
+ // 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.
74
+ function appendPixel ( pixel , ffmpegStdin ) {
75
+ if ( currentOffset + 3 > frameSize ) {
76
+ flushFrameBuffer ( ffmpegStdin ) ;
77
+ }
78
+ currentFrameBuffer [ currentOffset ] = pixel . r ;
79
+ currentFrameBuffer [ currentOffset + 1 ] = pixel . g ;
80
+ currentFrameBuffer [ currentOffset + 2 ] = pixel . b ;
81
+ currentOffset += 3 ;
82
+ if ( currentOffset === frameSize ) {
83
+ ffmpegStdin . write ( currentFrameBuffer ) ;
84
+ currentFrameBuffer = Buffer . alloc ( frameSize ) ;
85
+ currentOffset = 0 ;
86
+ }
87
+ }
88
+
89
+ // Pokud zbyde částečný snímek, doplní ho černou barvou a odešle.
90
+ function flushFrameBuffer ( ffmpegStdin ) {
91
+ if ( currentOffset > 0 ) {
92
+ currentFrameBuffer . fill ( 0 , currentOffset ) ;
93
+ ffmpegStdin . write ( currentFrameBuffer ) ;
94
+ currentFrameBuffer = Buffer . alloc ( frameSize ) ;
95
+ currentOffset = 0 ;
96
+ }
97
+ }
98
+
99
+ // Převod datového chunku na pole pixelů (každý byte se převede na dvě barvy podle hexadecimální hodnoty)
100
+ function processDataChunk ( chunk ) {
101
+ let pixels = [ ] ;
102
+ for ( let i = 0 ; i < chunk . length ; i ++ ) {
103
+ const byte = chunk [ i ] ;
104
+ const highNibble = ( byte >> 4 ) . toString ( 16 ) . toUpperCase ( ) ;
105
+ const lowNibble = ( byte & 0x0F ) . toString ( 16 ) . toUpperCase ( ) ;
106
+ pixels . push ( getColorFromNibble ( highNibble ) ) ;
107
+ pixels . push ( getColorFromNibble ( lowNibble ) ) ;
108
+ }
109
+ return pixels ;
110
+ }
111
+
112
+ // Získá všechny soubory z adresáře (rekurzivně)
113
+ function getAllFiles ( dir , fileList = [ ] ) {
114
+ const files = fs . readdirSync ( dir ) ;
115
+ files . forEach ( file => {
116
+ const fullPath = path . join ( dir , file ) ;
117
+ if ( fs . statSync ( fullPath ) . isDirectory ( ) ) {
118
+ getAllFiles ( fullPath , fileList ) ;
119
+ } else {
120
+ fileList . push ( fullPath ) ;
121
+ }
122
+ } ) ;
123
+ return fileList ;
124
+ }
125
+
126
+ async function main ( ) {
127
+ const inputPath = process . argv [ 2 ] || prompt ( "Define input file/directory: " ) ;
128
+ const outputVideoPath = path . join ( __dirname , "./../output_video/out.mkv" ) ;
129
+ let files = [ ] ;
130
+ if ( fs . statSync ( inputPath ) . isDirectory ( ) ) {
131
+ files = getAllFiles ( inputPath ) ;
132
+ } else {
133
+ files . push ( inputPath ) ;
134
+ }
135
+
136
+ console . log ( `Budu zpracovávat ${ files . length } souborů.` ) ;
137
+
138
+ // Spuštění ffmpeg pro kódování raw videa do lossless videa pomocí ffv1
139
+ const ffmpegArgs = [
140
+ "-y" ,
141
+ "-f" ,
142
+ "rawvideo" ,
143
+ "-pixel_format" ,
144
+ "rgb24" ,
145
+ "-video_size" ,
146
+ `${ videoWidth } x${ videoHeight } ` ,
147
+ "-framerate" ,
148
+ "30" ,
149
+ "-i" ,
150
+ "-" ,
151
+ "-c:v" ,
152
+ "ffv1" ,
153
+ "-preset" ,
154
+ "ultrafast" ,
155
+ outputVideoPath ,
156
+ ] ;
157
+ const ffmpeg = spawn ( "ffmpeg" , ffmpegArgs ) ;
158
+
159
+ ffmpeg . on ( "error" , ( err ) => {
160
+ console . error ( "Chyba ffmpeg:" , err ) ;
161
+ process . exit ( 1 ) ;
162
+ } ) ;
163
+
164
+ ffmpeg . stderr . on ( "data" , ( data ) => {
165
+ // Volitelně můžeš logovat progres; zatím zakomentováno
166
+ // console.error(data.toString());
167
+ } ) ;
168
+
169
+ // Použijeme číslovaný cyklus, abychom věděli, jestli zpracováváme poslední soubor
170
+ for ( let i = 0 ; i < files . length ; i ++ ) {
171
+ const file = files [ i ] ;
172
+ console . log ( `Zpracovávám soubor: ${ file } ` ) ;
173
+ const fileSize = fs . statSync ( file ) . size ;
174
+ const progressManager = new ProgressManager ( fileSize , path . basename ( file ) ) ;
175
+
176
+ // Vložíme název souboru jako marker
177
+ pushFilename ( file , ( pixel ) => {
178
+ appendPixel ( pixel , ffmpeg . stdin ) ;
179
+ } ) ;
180
+
181
+ await new Promise ( ( resolve , reject ) => {
182
+ const stream = fs . createReadStream ( file , { highWaterMark : 1024 * 1024 } ) ;
183
+ stream . on ( "data" , ( chunk ) => {
184
+ progressManager . update ( chunk . length ) ;
185
+ const pixels = processDataChunk ( chunk ) ;
186
+ for ( const pixel of pixels ) {
187
+ appendPixel ( pixel , ffmpeg . stdin ) ;
188
+ }
189
+ } ) ;
190
+ stream . on ( "end" , ( ) => {
191
+ // Vložíme terminátor – marker D: pixel s hodnotami {128, 0, 128}
192
+ appendPixel ( { r : 128 , g : 0 , b : 128 } , ffmpeg . stdin ) ;
193
+ // Pokud je to poslední soubor, doplníme aktuální snímek (neúplný blok) černými pixely a flushneme ho.
194
+ if ( i === files . length - 1 ) {
195
+ flushFrameBuffer ( ffmpeg . stdin ) ;
196
+ }
197
+ console . log ( `Dokončeno: ${ file } ` ) ;
198
+ resolve ( ) ;
199
+ } ) ;
200
+ stream . on ( "error" , ( err ) => reject ( err ) ) ;
201
+ } ) ;
202
+ }
203
+ // Po zpracování všech souborů flushneme zůstalé pixely a ukončíme stdin ffmpeg
204
+ flushFrameBuffer ( ffmpeg . stdin ) ;
205
+ ffmpeg . stdin . end ( ) ;
206
+
207
+ ffmpeg . on ( "close" , ( code ) => {
208
+ if ( code === 0 ) {
209
+ console . log ( "Video bylo úspěšně vytvořeno!" ) ;
210
+ } else {
211
+ console . error ( `FFmpeg skončil s kódem ${ code } ` ) ;
212
+ }
213
+ } ) ;
214
+ }
215
+
216
+ main ( ) . catch ( ( err ) => {
217
+ console . error ( "Chyba v dataToVideo.js:" , err ) ;
218
+ } ) ;
0 commit comments