Skip to content

Commit f1c045c

Browse files
danielyan86129meta-codesync[bot]
authored andcommitted
Add verbatim stream copying support (#360)
Summary: Pull Request resolved: #360 - Add verbatim_copy() function and VRSWriter methods (add_verbatim_copy_streams/copy_verbatim_records) for efficient C++ verbatim VRS stream copying - Add getRecordFileReader() accessor to OssVRSReader - Add tests and regenerate pyi stubs Reviewed By: kongchen1992 Differential Revision: D96868589
1 parent 263fb83 commit f1c045c

9 files changed

Lines changed: 590 additions & 1 deletion

File tree

csrc/reader/VRSReader.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ class OssVRSReader : public VRSReaderBase {
161161
/// Initialize the module by calling initVrsBindings()
162162
void init();
163163

164+
/// Get a reference to the underlying RecordFileReader.
165+
RecordFileReader& getRecordFileReader() {
166+
return reader_;
167+
}
168+
164169
void open(const string& path);
165170
void open(const PyFileSpec& spec);
166171

csrc/writer/VRSWriter.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,37 @@ int VRSWriter::close() {
138138
return writer_.waitForFileClosed();
139139
}
140140

141+
int VRSWriter::addVerbatimCopyStreams(
142+
RecordFileReader& reader,
143+
const std::vector<std::string>& streamIds) {
144+
verbatimReader_ = &reader;
145+
verbatimCopyOptions_ = std::make_unique<vrs::utils::CopyOptions>(false);
146+
for (const auto& sidStr : streamIds) {
147+
auto sid = StreamId::fromNumericName(sidStr);
148+
if (!sid.isValid()) {
149+
throw py::value_error("Invalid stream ID: " + sidStr);
150+
}
151+
verbatimCopiers_.push_back(
152+
std::make_unique<vrs::utils::Copier>(reader, writer_, sid, *verbatimCopyOptions_));
153+
verbatimStreamIds_.insert(sid);
154+
}
155+
return 0;
156+
}
157+
158+
int VRSWriter::copyVerbatimRecords() {
159+
if (!verbatimReader_ || verbatimStreamIds_.empty()) {
160+
return 0;
161+
}
162+
const auto& index = verbatimReader_->getIndex();
163+
for (const auto& record : index) {
164+
if (verbatimStreamIds_.count(record.streamId) > 0) {
165+
int status = verbatimReader_->readRecord(record);
166+
if (status != 0) {
167+
return status;
168+
}
169+
}
170+
}
171+
return 0;
172+
}
173+
141174
} // namespace pyvrs

csrc/writer/VRSWriter.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma once
1818

1919
#include <memory>
20+
#include <set>
2021
#include <string>
2122
#include <vector>
2223

@@ -29,8 +30,10 @@
2930
#endif
3031

3132
#include <vrs/DataLayout.h>
33+
#include <vrs/RecordFileReader.h>
3234
#include <vrs/RecordFileWriter.h>
3335
#include <vrs/Recordable.h>
36+
#include <vrs/utils/FilterCopyHelpers.h>
3437

3538
#include "PyDataPiece.h"
3639

@@ -76,6 +79,18 @@ class VRSWriter {
7679

7780
void addRecordable(Recordable* recordable);
7881

82+
/// Register streams from a RecordFileReader for verbatim copying.
83+
/// Copiers are created that will copy records for the specified streams.
84+
/// Must be called before create() / first writeRecords().
85+
/// WARNING: The reader must remain alive until after copyVerbatimRecords() is called.
86+
int addVerbatimCopyStreams(RecordFileReader& reader, const std::vector<std::string>& streamIds);
87+
88+
/// Copy all registered verbatim stream records into the writer.
89+
/// Reads records from the reader for the registered streams and copies them verbatim.
90+
/// Call after all processed records have been written.
91+
/// NOTE: Assumes the reader passed to addVerbatimCopyStreams() is still valid.
92+
int copyVerbatimRecords();
93+
7994
int writeRecords(double maxTimestamp);
8095

8196
uint64_t getBackgroundThreadQueueByteSize();
@@ -89,6 +104,11 @@ class VRSWriter {
89104
private:
90105
RecordFileWriter writer_;
91106
std::vector<std::unique_ptr<PyStream>> streams_;
107+
// Non-owning pointer; caller must keep reader alive until after copyVerbatimRecords() is called.
108+
RecordFileReader* verbatimReader_ = nullptr;
109+
std::vector<std::unique_ptr<vrs::utils::Copier>> verbatimCopiers_;
110+
std::set<StreamId> verbatimStreamIds_;
111+
std::unique_ptr<vrs::utils::CopyOptions> verbatimCopyOptions_;
92112
};
93113

94114
} // namespace pyvrs

csrc/writer/Writer.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
#include <pybind11/stl.h>
3434

3535
#include <vrs/os/Platform.h>
36+
#include <vrs/utils/FilterCopy.h>
3637

38+
#include "../VrsBindings.h"
39+
#include "../reader/VRSReader.h"
3740
#include "PyRecordable.h"
3841
#include "VRSWriter.h"
3942

@@ -73,6 +76,38 @@ using namespace vrs;
7376
}));
7477

7578
void pybind_writer(py::module& m) {
79+
m.def(
80+
"verbatim_copy",
81+
[](const std::string& inputPath,
82+
const std::string& outputPath,
83+
const std::vector<std::string>& streamIds) {
84+
initVrsBindings();
85+
vrs::utils::FilteredFileReader filteredReader(inputPath);
86+
int status = filteredReader.openFile();
87+
if (status != 0) {
88+
throw py::value_error("Failed to open input file: " + inputPath);
89+
}
90+
if (!streamIds.empty()) {
91+
filteredReader.filter.streams.clear();
92+
for (const auto& sid : streamIds) {
93+
auto id = StreamId::fromNumericName(sid);
94+
if (!id.isValid()) {
95+
throw py::value_error("Invalid stream ID: " + sid);
96+
}
97+
filteredReader.filter.streams.insert(id);
98+
}
99+
}
100+
vrs::utils::CopyOptions copyOptions(false);
101+
status = vrs::utils::filterCopy(filteredReader, outputPath, copyOptions);
102+
if (status != 0) {
103+
throw std::runtime_error("verbatim_copy failed with error code " + to_string(status));
104+
}
105+
return status;
106+
},
107+
py::arg("input_path"),
108+
py::arg("output_path"),
109+
py::arg("stream_ids") = std::vector<std::string>{});
110+
76111
py::class_<pyvrs::PyRecordFormat, std::unique_ptr<pyvrs::PyRecordFormat, py::nodelete>>(
77112
m, "RecordFormat")
78113
.def("getMembers", &pyvrs::PyRecordFormat::getMembers)
@@ -112,6 +147,16 @@ void pybind_writer(py::module& m) {
112147
.def("writeRecords", &pyvrs::VRSWriter::writeRecords)
113148
.def("getBackgroundThreadQueueByteSize", &pyvrs::VRSWriter::getBackgroundThreadQueueByteSize)
114149
.def("close", &pyvrs::VRSWriter::close)
150+
.def(
151+
"addVerbatimCopyStreams",
152+
[](pyvrs::VRSWriter& self,
153+
PyVRSReader& reader,
154+
const std::vector<std::string>& streamIds) {
155+
return self.addVerbatimCopyStreams(reader.getRecordFileReader(), streamIds);
156+
},
157+
py::arg("reader"),
158+
py::arg("stream_ids"))
159+
.def("copyVerbatimRecords", &pyvrs::VRSWriter::copyVerbatimRecords)
115160
#if IS_VRS_FB_INTERNAL()
116161
#include "Writer_fb.hpp"
117162
#endif

pyvrs/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
StreamNotFoundError,
4343
TimestampNotFoundError,
4444
verbatim_checksum,
45+
verbatim_copy,
4546
VRSRecord,
4647
Writer,
4748
)
@@ -79,6 +80,7 @@
7980
"StreamNotFoundError",
8081
"TimestampNotFoundError",
8182
"verbatim_checksum",
83+
"verbatim_copy",
8284
"VRSRecord",
8385
"Writer",
8486
]

pyvrs/writer.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515

1616
import time
17-
from typing import Dict, List, Union
17+
from typing import Dict, List, Sequence, Union
1818

1919
import numpy as np
2020

@@ -101,6 +101,30 @@ def _create(self):
101101
def close(self) -> None:
102102
return self._writer.close()
103103

104+
def add_verbatim_copy_streams(self, reader, stream_ids: Sequence[str]) -> int:
105+
"""Register streams from a reader for verbatim copying into this writer.
106+
107+
Must be called before the first flush_records() call.
108+
The reader must remain open until after copy_verbatim_records() is called.
109+
110+
Args:
111+
reader: A pyvrs Reader (C++ binding object, e.g. SyncVRSReader._reader).
112+
stream_ids: Stream IDs to copy verbatim (e.g. ["281-1", "282-1"]).
113+
"""
114+
if self.file_created:
115+
raise Exception(
116+
"add_verbatim_copy_streams must be called before flush_records"
117+
)
118+
return self._writer.addVerbatimCopyStreams(reader, list(stream_ids))
119+
120+
def copy_verbatim_records(self) -> int:
121+
"""Copy all registered verbatim stream records into the output file.
122+
123+
Call after all processed records have been written, before close().
124+
"""
125+
self._create()
126+
return self._writer.copyVerbatimRecords()
127+
104128
# Recordable instance ids are automatically assigned when Recordable objects are created.
105129
# This guarantees that each Recordable gets a unique ID.
106130
# WARNING! If your code relies on specific instance IDs, your design is weak, and you are

0 commit comments

Comments
 (0)