Skip to content

Commit 54e8a3a

Browse files
mamoreau-devolutionsawakecoding
authored andcommitted
generate recording.json manifest file
1 parent bce1b24 commit 54e8a3a

5 files changed

Lines changed: 306 additions & 8 deletions

File tree

dll/ArrayList.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ void* MsRdpEx_ArrayList_GetHead(MsRdpEx_ArrayList* ctx)
346346
if (ctx->synchronized)
347347
EnterCriticalSection(&ctx->lock);
348348

349-
if (ctx->count > 1)
349+
if (ctx->count >= 1)
350350
{
351351
index = 0;
352352
obj = ctx->array[index];
@@ -370,7 +370,7 @@ void* MsRdpEx_ArrayList_GetTail(MsRdpEx_ArrayList* ctx)
370370
if (ctx->synchronized)
371371
EnterCriticalSection(&ctx->lock);
372372

373-
if (ctx->count > 1)
373+
if (ctx->count >= 1)
374374
{
375375
index = ctx->count - 1;
376376
obj = ctx->array[index];

dll/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ set(MSRDPEX_HEADERS
3333
"${MSRDPEX_INCLUDE_DIR}/RdpSettings.h"
3434
"${MSRDPEX_INCLUDE_DIR}/OutputMirror.h"
3535
"${MSRDPEX_INCLUDE_DIR}/VideoRecorder.h"
36+
"${MSRDPEX_INCLUDE_DIR}/RecordingManifest.h"
3637
"${MSRDPEX_INCLUDE_DIR}/MsRdpEx.h")
3738

3839
set(MSRDPEX_SOURCES
@@ -57,6 +58,7 @@ set(MSRDPEX_SOURCES
5758
ApiHooks.cpp
5859
OutputMirror.c
5960
VideoRecorder.c
61+
RecordingManifest.c
6062
Pcap.cpp
6163
Bitmap.c
6264
WinMsg.c

dll/OutputMirror.c

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <MsRdpEx/Environment.h>
55
#include <MsRdpEx/Stopwatch.h>
66
#include <MsRdpEx/VideoRecorder.h>
7+
#include <MsRdpEx/RecordingManifest.h>
78
#include <MsRdpEx/OutputMirror.h>
89

910
struct _MsRdpEx_OutputMirror
@@ -20,14 +21,16 @@ struct _MsRdpEx_OutputMirror
2021
HGDIOBJ hShadowObject;
2122
uint32_t captureIndex;
2223
uint64_t captureBaseTime;
23-
24+
int64_t startTime;
2425
int videoRecordingCount;
2526
bool dumpBitmapUpdates;
2627
bool videoRecordingEnabled;
2728
uint32_t videoQualityLevel;
2829
char recordingPath[MSRDPEX_MAX_PATH];
30+
char outputPath[MSRDPEX_MAX_PATH];
2931
char sessionId[MSRDPEX_GUID_STRING_SIZE];
3032
MsRdpEx_VideoRecorder* videoRecorder;
33+
MsRdpEx_RecordingManifest* manifest;
3134
FILE* frameMetadataFile;
3235

3336
CRITICAL_SECTION lock;
@@ -179,16 +182,27 @@ bool MsRdpEx_OutputMirror_Init(MsRdpEx_OutputMirror* ctx)
179182
}
180183

181184
if (ctx->videoRecordingEnabled) {
182-
char outputPath[MSRDPEX_MAX_PATH];
183185
char filename[MSRDPEX_MAX_PATH];
184186

185-
sprintf_s(outputPath, MSRDPEX_MAX_PATH, "%s\\%s", ctx->recordingPath, ctx->sessionId);
186-
MsRdpEx_MakePath(outputPath, NULL);
187+
if (!ctx->manifest) {
188+
GUID sessionId;
189+
MsRdpEx_GuidStrToBin(ctx->sessionId, &sessionId, 0);
190+
ctx->startTime = MsRdpEx_GetUnixTime();
191+
ctx->manifest = MsRdpEx_RecordingManifest_New();
192+
MsRdpEx_RecordingManifest_SetSessionId(ctx->manifest, &sessionId);
193+
MsRdpEx_RecordingManifest_SetStartTime(ctx->manifest, ctx->startTime);
194+
}
195+
196+
sprintf_s(ctx->outputPath, MSRDPEX_MAX_PATH, "%s\\%s", ctx->recordingPath, ctx->sessionId);
197+
MsRdpEx_MakePath(ctx->outputPath, NULL);
187198

188199
ctx->videoRecorder = MsRdpEx_VideoRecorder_New();
189200

190201
if (ctx->videoRecorder) {
191-
sprintf_s(filename, MSRDPEX_MAX_PATH, "%s\\recording-%d.webm", outputPath, ctx->videoRecordingCount);
202+
int64_t startTime = (ctx->videoRecordingCount < 1) ? ctx->startTime : MsRdpEx_GetUnixTime();
203+
sprintf_s(filename, MSRDPEX_MAX_PATH, "%s\\recording-%d.webm", ctx->outputPath, ctx->videoRecordingCount);
204+
MsRdpEx_RecordingManifest_FinalizeFile(ctx->manifest, 0);
205+
MsRdpEx_RecordingManifest_AddFile(ctx->manifest, MsRdpEx_FileBase(filename), startTime, 0);
192206
ctx->videoRecordingCount++;
193207
MsRdpEx_VideoRecorder_SetFrameSize(ctx->videoRecorder, ctx->bitmapWidth, ctx->bitmapHeight);
194208
MsRdpEx_VideoRecorder_SetFileName(ctx->videoRecorder, filename);
@@ -199,7 +213,7 @@ bool MsRdpEx_OutputMirror_Init(MsRdpEx_OutputMirror* ctx)
199213
if (ctx->dumpBitmapUpdates) {
200214
char metadata[1024];
201215
sprintf_s(metadata, sizeof(metadata), "FrameTime|FrameSize|FrameFile|UpdatePos|UpdateSize\n");
202-
sprintf_s(filename, MSRDPEX_MAX_PATH, "%s\\frame_meta.psv", outputPath);
216+
sprintf_s(filename, MSRDPEX_MAX_PATH, "%s\\frame_meta.psv", ctx->outputPath);
203217
ctx->frameMetadataFile = MsRdpEx_FileOpen(filename, "wb");
204218
fwrite(metadata, 1, strlen(metadata), ctx->frameMetadataFile);
205219
}
@@ -239,6 +253,22 @@ bool MsRdpEx_OutputMirror_Uninit(MsRdpEx_OutputMirror* ctx)
239253
return true;
240254
}
241255

256+
bool MsRdpEx_OutputMirror_WriteManifestFile(MsRdpEx_OutputMirror* ctx)
257+
{
258+
bool success = false;
259+
char filename[MSRDPEX_MAX_PATH];
260+
261+
if (!ctx->manifest)
262+
return false;
263+
264+
MsRdpEx_RecordingManifest_FinalizeFile(ctx->manifest, 0);
265+
266+
sprintf_s(filename, MSRDPEX_MAX_PATH, "%s\\recording.json", ctx->outputPath);
267+
success = MsRdpEx_RecordingManifest_WriteJsonFile(ctx->manifest, filename);
268+
269+
return success;
270+
}
271+
242272
MsRdpEx_OutputMirror* MsRdpEx_OutputMirror_New()
243273
{
244274
MsRdpEx_OutputMirror* ctx;
@@ -263,8 +293,15 @@ void MsRdpEx_OutputMirror_Free(MsRdpEx_OutputMirror* ctx)
263293
if (!ctx)
264294
return;
265295

296+
MsRdpEx_OutputMirror_WriteManifestFile(ctx);
297+
266298
MsRdpEx_OutputMirror_Uninit(ctx);
267299

300+
if (ctx->manifest) {
301+
MsRdpEx_RecordingManifest_Free(ctx->manifest);
302+
ctx->manifest = NULL;
303+
}
304+
268305
DeleteCriticalSection(&ctx->lock);
269306

270307
free(ctx);

dll/RecordingManifest.c

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
2+
#include <MsRdpEx/ArrayList.h>
3+
4+
#include <MsRdpEx/RecordingManifest.h>
5+
6+
struct _MsRdpEx_JrecFile
7+
{
8+
char* fileName;
9+
int64_t startTime;
10+
int64_t duration;
11+
};
12+
13+
struct _MsRdpEx_RecordingManifest
14+
{
15+
GUID sessionId;
16+
int64_t startTime;
17+
int64_t duration;
18+
MsRdpEx_ArrayList* files;
19+
};
20+
21+
void MsRdpEx_JrecFile_SetFileName(MsRdpEx_JrecFile* file, const char* fileName)
22+
{
23+
if (file->fileName) {
24+
free(file->fileName);
25+
file->fileName = NULL;
26+
}
27+
28+
if (fileName) {
29+
file->fileName = _strdup(fileName);
30+
}
31+
}
32+
33+
MsRdpEx_JrecFile* MsRdpEx_JrecFile_New()
34+
{
35+
MsRdpEx_JrecFile* file;
36+
37+
file = (MsRdpEx_JrecFile*) calloc(1, sizeof(MsRdpEx_JrecFile));
38+
39+
if (!file)
40+
return NULL;
41+
42+
return file;
43+
}
44+
45+
void MsRdpEx_JrecFile_Free(MsRdpEx_JrecFile* file)
46+
{
47+
if (!file)
48+
return;
49+
50+
MsRdpEx_JrecFile_SetFileName(file, NULL);
51+
free(file);
52+
}
53+
54+
void MsRdpEx_RecordingManifest_SetSessionId(MsRdpEx_RecordingManifest* ctx, GUID* sessionId)
55+
{
56+
MsRdpEx_GuidCopy(&ctx->sessionId, sessionId);
57+
}
58+
59+
void MsRdpEx_RecordingManifest_SetStartTime(MsRdpEx_RecordingManifest* ctx, int64_t startTime)
60+
{
61+
ctx->startTime = startTime;
62+
}
63+
64+
void MsRdpEx_RecordingManifest_SetDuration(MsRdpEx_RecordingManifest* ctx, int64_t duration)
65+
{
66+
ctx->duration = duration;
67+
}
68+
69+
bool MsRdpEx_RecordingManifest_AddFile(MsRdpEx_RecordingManifest* ctx, const char* fileName, int64_t startTime, int64_t duration)
70+
{
71+
MsRdpEx_JrecFile* file;
72+
73+
file = MsRdpEx_JrecFile_New();
74+
75+
if (!file)
76+
return false;
77+
78+
MsRdpEx_JrecFile_SetFileName(file, fileName);
79+
file->startTime = startTime;
80+
file->duration = duration;
81+
82+
MsRdpEx_ArrayList_Add(ctx->files, (void*)file);
83+
84+
return true;
85+
}
86+
87+
void MsRdpEx_RecordingManifest_FinalizeFile(MsRdpEx_RecordingManifest* ctx, int64_t endTime)
88+
{
89+
MsRdpEx_JrecFile* file;
90+
91+
if (!endTime)
92+
endTime = MsRdpEx_GetUnixTime();
93+
94+
file = (MsRdpEx_JrecFile*) MsRdpEx_ArrayList_GetTail(ctx->files);
95+
96+
if (file) {
97+
if (!file->duration) {
98+
file->duration = endTime - file->startTime;
99+
}
100+
}
101+
}
102+
103+
bool MsRdpEx_RecordingManifest_UpdateTotalDuration(MsRdpEx_RecordingManifest* ctx)
104+
{
105+
int64_t totalDuration = 0;
106+
107+
MsRdpEx_ArrayListIt* it = MsRdpEx_ArrayList_It(ctx->files, MSRDPEX_ITERATOR_FLAG_EXCLUSIVE);
108+
109+
while (!MsRdpEx_ArrayListIt_Done(it))
110+
{
111+
MsRdpEx_JrecFile* file = (MsRdpEx_JrecFile*) MsRdpEx_ArrayListIt_Next(it);
112+
totalDuration += file->duration;
113+
}
114+
115+
MsRdpEx_ArrayListIt_Finish(it);
116+
117+
ctx->duration = totalDuration;
118+
119+
return true;
120+
}
121+
122+
char* MsRdpEx_RecordingManifest_WriteJsonData(MsRdpEx_RecordingManifest* ctx)
123+
{
124+
char fileData[512];
125+
char* jsonData = NULL;
126+
char sessionId[MSRDPEX_GUID_STRING_SIZE];
127+
MsRdpEx_GuidBinToStr((GUID*)&ctx->sessionId, sessionId, 0);
128+
129+
MsRdpEx_RecordingManifest_UpdateTotalDuration(ctx);
130+
131+
char header[512];
132+
sprintf_s(header, sizeof(header),
133+
"{\n"
134+
" \"sessionId\": \"%s\",\n"
135+
" \"startTime\": %lld,\n"
136+
" \"duration\": %lld,\n"
137+
" \"files\": [\n",
138+
sessionId, ctx->startTime, ctx->duration);
139+
140+
const char* footer = " ]\n}";
141+
142+
MsRdpEx_ArrayListIt* it = MsRdpEx_ArrayList_It(ctx->files, MSRDPEX_ITERATOR_FLAG_EXCLUSIVE);
143+
144+
int numFiles = MsRdpEx_ArrayList_Count(ctx->files);
145+
size_t bufferSize = strlen(header) + strlen(footer) + (numFiles * sizeof(fileData)) + 1;
146+
147+
jsonData = (char*) calloc(bufferSize, 1);
148+
149+
if (!jsonData) {
150+
MsRdpEx_ArrayListIt_Finish(it);
151+
return NULL;
152+
}
153+
154+
strcat_s(jsonData, bufferSize, header);
155+
156+
int fileIndex = 0;
157+
while (!MsRdpEx_ArrayListIt_Done(it))
158+
{
159+
MsRdpEx_JrecFile* file = (MsRdpEx_JrecFile*) MsRdpEx_ArrayListIt_Next(it);
160+
161+
sprintf_s(fileData, sizeof(fileData) - 1,
162+
" {\n"
163+
" \"fileName\": \"%s\",\n"
164+
" \"startTime\": %lld,\n"
165+
" \"duration\": %lld\n"
166+
" }%s\n",
167+
file->fileName, file->startTime, file->duration,
168+
((fileIndex + 1) < numFiles) ? "," : "");
169+
170+
strcat_s(jsonData, bufferSize, fileData);
171+
fileIndex++;
172+
}
173+
174+
strcat_s(jsonData, bufferSize, footer);
175+
176+
MsRdpEx_ArrayListIt_Finish(it);
177+
178+
return jsonData;
179+
}
180+
181+
bool MsRdpEx_RecordingManifest_WriteJsonFile(MsRdpEx_RecordingManifest* ctx, const char* filename)
182+
{
183+
bool success = false;
184+
char* jsonData = MsRdpEx_RecordingManifest_WriteJsonData(ctx);
185+
186+
if (jsonData) {
187+
success = MsRdpEx_FileSave(filename, (uint8_t*)jsonData, strlen(jsonData), 0);
188+
free(jsonData);
189+
}
190+
191+
return success;
192+
}
193+
194+
MsRdpEx_RecordingManifest* MsRdpEx_RecordingManifest_New()
195+
{
196+
MsRdpEx_RecordingManifest* ctx;
197+
198+
ctx = (MsRdpEx_RecordingManifest*) calloc(1, sizeof(MsRdpEx_RecordingManifest));
199+
200+
if (!ctx)
201+
return NULL;
202+
203+
ctx->files = MsRdpEx_ArrayList_New(true);
204+
205+
if (!ctx->files)
206+
goto error;
207+
208+
MsRdpEx_ArrayList_Object(ctx->files)->fnObjectFree =
209+
(MSRDPEX_OBJECT_FREE_FN) MsRdpEx_JrecFile_Free;
210+
211+
return ctx;
212+
error:
213+
MsRdpEx_RecordingManifest_Free(ctx);
214+
return NULL;
215+
}
216+
217+
void MsRdpEx_RecordingManifest_Free(MsRdpEx_RecordingManifest* ctx)
218+
{
219+
if (!ctx)
220+
return;
221+
222+
if (ctx->files) {
223+
MsRdpEx_ArrayList_Free(ctx->files);
224+
ctx->files = NULL;
225+
}
226+
227+
free(ctx);
228+
}

0 commit comments

Comments
 (0)