+ // TODO: Look up in the SQLite database.
+
+ FILE *fp = fopen(filename, "rb");
+ if (fp == nullptr) {
+ perror(filename);
+ exit(1);
+ }
+
+ size_t magic_offset = 0;
+ size_t skipped_bytes = 0;
+ while (!feof(fp) && !ferror(fp)) {
+ int ch = getc(fp);
+ if (ch == -1) {
+ break;
+ }
+ if (ch != frame_magic[magic_offset++]) {
+ skipped_bytes += magic_offset;
+ magic_offset = 0;
+ continue;
+ }
+ if (magic_offset < frame_magic_len) {
+ // Still reading the magic (hopefully).
+ continue;
+ }
+
+ // OK, found the magic. Try to parse the frame header.
+ magic_offset = 0;
+
+ if (skipped_bytes > 0) {
+ fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes in the middle.\n",
+ filename, skipped_bytes);
+ skipped_bytes = 0;
+ }
+
+ uint32_t len;
+ if (fread(&len, sizeof(len), 1, fp) != 1) {
+ fprintf(stderr, "WARNING: %s: Short read when getting length.\n", filename);
+ break;
+ }
+
+ string serialized;
+ serialized.resize(ntohl(len));
+ if (fread(&serialized[0], serialized.size(), 1, fp) != 1) {
+ fprintf(stderr, "WARNING: %s: Short read when reading frame header (%zu bytes).\n", filename, serialized.size());
+ break;
+ }
+
+ FrameHeaderProto hdr;
+ if (!hdr.ParseFromString(serialized)) {
+ fprintf(stderr, "WARNING: %s: Corrupted frame header.\n", filename);
+ continue;
+ }
+
+ FrameOnDisk frame;
+ frame.pts = hdr.pts();
+ frame.offset = ftell(fp);
+ frame.filename_idx = filename_idx;
+ frame.size = hdr.file_size();
+
+ if (fseek(fp, frame.offset + frame.size, SEEK_SET) == -1) {
+ fprintf(stderr, "WARNING: %s: Could not seek past frame (probably truncated).\n", filename);
+ continue;
+ }
+
+ if (hdr.stream_idx() >= 0 && hdr.stream_idx() < MAX_STREAMS) {
+ frames[hdr.stream_idx()].push_back(frame);
+ start_pts = max(start_pts, hdr.pts());
+ }
+ }
+
+ if (skipped_bytes > 0) {
+ fprintf(stderr, "WARNING: %s: Skipped %zu garbage bytes at the end.\n",
+ filename, skipped_bytes);
+ }
+}
+
+void load_existing_frames()
+{
+ string frame_dir = global_flags.working_directory + "/frames";
+ DIR *dir = opendir(frame_dir.c_str());
+ if (dir == nullptr) {
+ perror("frames/");
+ start_pts = 0;
+ return;
+ }
+
+ for ( ;; ) {
+ errno = 0;
+ dirent *de = readdir(dir);
+ if (de == nullptr) {
+ if (errno != 0) {
+ perror("readdir");
+ exit(1);
+ }
+ break;
+ }
+
+ if (de->d_type == DT_REG) {
+ string filename = frame_dir + "/" + de->d_name;
+ load_frame_file(filename.c_str(), frame_filenames.size());
+ frame_filenames.push_back(filename);
+ }
+ }
+
+ closedir(dir);
+
+ if (start_pts == -1) {
+ start_pts = 0;
+ } else {
+ // Add a gap of one second from the old frames to the new ones.
+ start_pts += TIMEBASE;
+ }
+
+ for (int stream_idx = 0; stream_idx < MAX_STREAMS; ++stream_idx) {
+ sort(frames[stream_idx].begin(), frames[stream_idx].end(),
+ [](const auto &a, const auto &b) { return a.pts < b.pts; });
+ }
+}
+
+int record_thread_func()
+{
+ auto format_ctx = avformat_open_input_unique(global_flags.stream_source.c_str(), nullptr, nullptr);