1
1
import { spawn } from 'child_process' ;
2
2
import fs from 'fs' ;
3
3
import path from 'path' ;
4
- import { uploadObjectFromDir } from './object ' ;
4
+ import { addVideoToQueue } from './queue ' ;
5
5
6
6
export const createFfmpegProcess = (
7
7
videoPort : number ,
@@ -11,23 +11,26 @@ export const createFfmpegProcess = (
11
11
audioPort ?: number ,
12
12
) => {
13
13
if ( type === 'record' ) {
14
- fs . mkdirSync ( `${ assetsDirPath } /records /${ roomId } ` , { recursive : true } ) ;
14
+ fs . mkdirSync ( `${ assetsDirPath } /videos /${ roomId } ` , { recursive : true } ) ;
15
15
}
16
+ const randomStr = crypto . randomUUID ( ) ;
16
17
const sdpString = audioPort ? createRecordSdpText ( videoPort , audioPort ) : createThumbnailSdpText ( videoPort ) ;
17
- const args = type === 'thumbnail' ? thumbnailArgs ( assetsDirPath , roomId ) : recordArgs ( assetsDirPath , roomId ) ;
18
+ const args =
19
+ type === 'thumbnail' ? thumbnailArgs ( assetsDirPath , roomId ) : recordArgs ( assetsDirPath , roomId , randomStr ) ;
18
20
const ffmpegProcess = spawn ( 'ffmpeg' , args ) ;
19
21
20
22
ffmpegProcess . stdin . write ( sdpString ) ;
21
23
ffmpegProcess . stdin . end ( ) ;
22
24
23
- handleFfmpegProcess ( ffmpegProcess , type , roomId , assetsDirPath ) ;
25
+ handleFfmpegProcess ( ffmpegProcess , type , roomId , assetsDirPath , randomStr ) ;
24
26
} ;
25
27
26
28
const handleFfmpegProcess = (
27
29
process : ReturnType < typeof spawn > ,
28
30
type : string ,
29
31
roomId : string ,
30
32
assetsDirPath : string ,
33
+ uuid : string ,
31
34
) => {
32
35
if ( process . stderr ) {
33
36
process . stderr . setEncoding ( 'utf-8' ) ;
@@ -40,7 +43,7 @@ const handleFfmpegProcess = (
40
43
process . on ( 'error' , error => console . error ( `[FFmpeg ${ type } ] error:` , error ) ) ;
41
44
process . on ( 'close' , async code => {
42
45
if ( type === 'record' ) {
43
- await stopRecord ( assetsDirPath , roomId ) ;
46
+ await stopRecord ( assetsDirPath , roomId , uuid ) ;
44
47
} else {
45
48
await stopMakeThumbnail ( assetsDirPath , roomId ) ;
46
49
}
@@ -98,49 +101,67 @@ const thumbnailArgs = (dirPath: string, roomId: string) => {
98
101
return commandArgs ;
99
102
} ;
100
103
101
- const recordArgs = ( dirPath : string , roomId : string ) => {
104
+ const recordArgs = ( dirPath : string , roomId : string , randomStr : string ) => {
102
105
const commandArgs = [
103
106
'-loglevel' ,
104
107
'info' , // 로그 활성화
105
108
'-protocol_whitelist' ,
106
109
'pipe,udp,rtp' , // 허용할 프로토콜 정의
107
110
'-f' ,
108
- 'sdp' , // SDP 입력 포맷
111
+ 'sdp' , // 입력 포맷
109
112
'-i' ,
110
113
'pipe:0' , // SDP를 파이프로 전달
111
- '-c:v' ,
112
- 'libx264' , // 비디오 코덱
113
- '-preset' ,
114
- 'superfast' ,
115
- '-profile:v' ,
116
- 'high' , // H.264 High 프로필
117
- '-level:v' ,
118
- '4.1' , // H.264 레벨 설정 (4.1)
119
- '-crf' ,
120
- '23' , // 비디오 품질 설정
121
- '-c:a' ,
122
- 'libmp3lame' , // 오디오 코덱
123
- '-b:a' ,
124
- '128k' , // 오디오 비트레이트
125
- '-ar' ,
126
- '48000' , // 오디오 샘플링 레이트
127
- '-af' ,
128
- 'aresample=async=1' , // 오디오 샘플 동기화
129
- '-ac' ,
130
- '2' ,
131
- '-f' ,
132
- 'hls' , // HLS 출력 포맷
133
- '-hls_time' ,
134
- '15' , // 각 세그먼트 길이 (초)
135
- '-hls_list_size' ,
136
- '0' , // 유지할 세그먼트 개수
137
- '-hls_segment_filename' ,
138
- `${ dirPath } /records/${ roomId } /segment_%03d.ts` , // HLS 세그먼트 파일 이름
139
- `${ dirPath } /records/${ roomId } /video.m3u8` , // HLS 플레이리스트 파일 이름
114
+ '-c' ,
115
+ 'copy' , // 코덱 재인코딩 없이 원본 저장
116
+ '-y' , // 파일 덮어쓰기 허용
117
+ `${ dirPath } /videos/${ roomId } /${ randomStr } .webm` ,
140
118
] ;
141
119
return commandArgs ;
142
120
} ;
143
121
122
+ // const recordArgs = (dirPath: string, roomId: string) => {
123
+ // const commandArgs = [
124
+ // '-loglevel',
125
+ // 'info', // 로그 활성화
126
+ // '-protocol_whitelist',
127
+ // 'pipe,udp,rtp', // 허용할 프로토콜 정의
128
+ // '-f',
129
+ // 'sdp', // SDP 입력 포맷
130
+ // '-i',
131
+ // 'pipe:0', // SDP를 파이프로 전달
132
+ // '-c:v',
133
+ // 'libx264', // 비디오 코덱
134
+ // '-preset',
135
+ // 'superfast',
136
+ // '-profile:v',
137
+ // 'high', // H.264 High 프로필
138
+ // '-level:v',
139
+ // '4.1', // H.264 레벨 설정 (4.1)
140
+ // '-crf',
141
+ // '23', // 비디오 품질 설정
142
+ // '-c:a',
143
+ // 'libmp3lame', // 오디오 코덱
144
+ // '-b:a',
145
+ // '128k', // 오디오 비트레이트
146
+ // '-ar',
147
+ // '48000', // 오디오 샘플링 레이트
148
+ // '-af',
149
+ // 'aresample=async=1', // 오디오 샘플 동기화
150
+ // '-ac',
151
+ // '2',
152
+ // '-f',
153
+ // 'hls', // HLS 출력 포맷
154
+ // '-hls_time',
155
+ // '15', // 각 세그먼트 길이 (초)
156
+ // '-hls_list_size',
157
+ // '0', // 유지할 세그먼트 개수
158
+ // '-hls_segment_filename',
159
+ // `${dirPath}/records/${roomId}/segment_%03d.ts`, // HLS 세그먼트 파일 이름
160
+ // `${dirPath}/records/${roomId}/video.m3u8`, // HLS 플레이리스트 파일 이름
161
+ // ];
162
+ // return commandArgs;
163
+ // };
164
+
144
165
async function stopMakeThumbnail ( assetsDirPath : string , roomId : string ) {
145
166
const thumbnailPath = path . join ( assetsDirPath , 'thumbnails' , `${ roomId } .jpg` ) ;
146
167
fs . unlink ( thumbnailPath , err => {
@@ -150,29 +171,35 @@ async function stopMakeThumbnail(assetsDirPath: string, roomId: string) {
150
171
} ) ;
151
172
}
152
173
153
- async function stopRecord ( assetsDirPath : string , roomId : string ) {
154
- const roomDirPath = path . join ( assetsDirPath , 'records' , roomId ) ;
155
- await uploadObjectFromDir ( roomId , assetsDirPath ) ;
156
- if ( fs . existsSync ( roomDirPath ) ) {
157
- await deleteAllFiles ( roomDirPath ) ;
158
- console . log ( `All files in ${ roomDirPath } deleted successfully.` ) ;
159
- }
174
+ async function stopRecord ( assetsDirPath : string , roomId : string , uuid : string ) {
175
+ const video = {
176
+ roomId,
177
+ randomStr : uuid ,
178
+ title : 'title' ,
179
+ } ;
180
+ addVideoToQueue ( video ) ;
181
+ // const roomDirPath = path.join(assetsDirPath, 'records', roomId, `${uuid}.webm`);
182
+ // await uploadObjectFromDir(roomId, assetsDirPath);
183
+ // if (fs.existsSync(roomDirPath)) {
184
+ // await deleteAllFiles(roomDirPath);
185
+ // console.log(`All files in ${roomDirPath} deleted successfully.`);
186
+ // }
160
187
}
161
188
162
- async function deleteAllFiles ( directoryPath : string ) : Promise < void > {
163
- try {
164
- const files = await fs . promises . readdir ( directoryPath , { withFileTypes : true } ) ;
165
- for ( const file of files ) {
166
- const fullPath = path . join ( directoryPath , file . name ) ;
167
- if ( file . isDirectory ( ) ) {
168
- await deleteAllFiles ( fullPath ) ; // 재귀적으로 디렉토리 삭제
169
- await fs . promises . rmdir ( fullPath ) ; // 빈 디렉토리 삭제
170
- } else {
171
- await fs . promises . unlink ( fullPath ) ; // 파일 삭제
172
- }
173
- }
174
- } catch ( error ) {
175
- console . error ( `Error deleting files in directory: ${ directoryPath } ` , error ) ;
176
- throw error ;
177
- }
178
- }
189
+ // async function deleteAllFiles(directoryPath: string): Promise<void> {
190
+ // try {
191
+ // const files = await fs.promises.readdir(directoryPath, { withFileTypes: true });
192
+ // for (const file of files) {
193
+ // const fullPath = path.join(directoryPath, file.name);
194
+ // if (file.isDirectory()) {
195
+ // await deleteAllFiles(fullPath); // 재귀적으로 디렉토리 삭제
196
+ // await fs.promises.rmdir(fullPath); // 빈 디렉토리 삭제
197
+ // } else {
198
+ // await fs.promises.unlink(fullPath); // 파일 삭제
199
+ // }
200
+ // }
201
+ // } catch (error) {
202
+ // console.error(`Error deleting files in directory: ${directoryPath}`, error);
203
+ // throw error;
204
+ // }
205
+ // }
0 commit comments