MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SoundFileReader.cpp
Go to the documentation of this file.
1#include "SoundFileReader.hpp"
3
4extern "C" {
5#include <libavcodec/avcodec.h>
6#include <libavformat/avformat.h>
7#include <libavutil/opt.h>
8#include <libavutil/samplefmt.h>
9#include <libswresample/swresample.h>
10}
11
12namespace MayaFlux::IO {
13
14// ============================================================================
15// FileRegion Implementation
16// ============================================================================
17
19{
20 if (start_coordinates.size() == 1 && end_coordinates.size() == 1) {
23 }
25 }
26
28 region.set_attribute("label", name);
29 region.set_attribute("type", type);
30
31 for (const auto& [key, value] : attributes) {
32 region.set_attribute(key, value);
33 }
34 return region;
35}
36
37std::unordered_map<std::string, Kakshya::RegionGroup>
38FileReader::regions_to_groups(const std::vector<FileRegion>& regions)
39{
40 std::unordered_map<std::string, Kakshya::RegionGroup> groups;
41
42 for (const auto& region : regions) {
43 auto& group = groups[region.type];
44 group.name = region.type;
45 group.add_region(region.to_region());
46 }
47
48 return groups;
49}
50
51// ============================================================================
52// Constructor/Destructor
53// ============================================================================
54
59
64
65// ============================================================================
66// File Operations
67// ============================================================================
68
69bool SoundFileReader::can_read(const std::string& filepath) const
70{
72 if (!probe.open(filepath))
73 return false;
74
75 const AVCodec* codec = nullptr;
76 int idx = probe.find_best_stream(AVMEDIA_TYPE_AUDIO,
77 reinterpret_cast<const void**>(&codec));
78 return idx >= 0 && codec != nullptr;
79}
80
81// =========================================================================
82// FileReader — open / close / is_open
83// =========================================================================
84
85bool SoundFileReader::open(const std::string& filepath, FileReadOptions options)
86{
87 std::unique_lock<std::shared_mutex> lock(m_context_mutex);
88
89 m_demux.reset();
90 m_audio.reset();
92 {
93 std::lock_guard<std::mutex> ml(m_metadata_mutex);
94 m_cached_metadata.reset();
95 m_cached_regions.clear();
96 }
98
99 auto resolved = resolve_path(filepath);
100 m_filepath = resolved;
101 m_options = options;
102
103 auto demux = std::make_shared<FFmpegDemuxContext>();
104 if (!demux->open(resolved)) {
105 set_error(demux->last_error());
106 return false;
107 }
108
110 auto audio = std::make_shared<AudioStreamContext>();
111 if (!audio->open(*demux, planar, m_target_sample_rate)) {
112 set_error(audio->last_error());
113 return false;
114 }
115
116 m_demux = std::move(demux);
117 m_audio = std::move(audio);
118
121
124
125 return true;
126}
127
129 std::shared_ptr<FFmpegDemuxContext> demux,
130 std::shared_ptr<AudioStreamContext> audio,
131 const std::string& filepath,
132 FileReadOptions options)
133{
134 std::unique_lock<std::shared_mutex> lock(m_context_mutex);
135
136 m_demux.reset();
137 m_audio.reset();
139 {
140 std::lock_guard<std::mutex> ml(m_metadata_mutex);
141 m_cached_metadata.reset();
142 m_cached_regions.clear();
143 }
144 clear_error();
145
146 if (!demux || !demux->is_open()) {
147 set_error("open_from_demux: demux context is null or not open");
148 return false;
149 }
150
151 if (!audio || !audio->is_valid()) {
152 set_error("open_from_demux: audio stream context is null or not valid");
153 return false;
154 }
155
156 m_filepath = filepath;
157 m_demux = std::move(demux);
158 m_audio = std::move(audio);
159
162
165
166 return true;
167}
168
170{
171 std::unique_lock<std::shared_mutex> lock(m_context_mutex);
172 m_audio.reset();
173 m_demux.reset();
175 m_filepath.clear();
176 {
177 std::lock_guard<std::mutex> ml(m_metadata_mutex);
178 m_cached_metadata.reset();
179 m_cached_regions.clear();
180 }
181}
182
184{
185 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
186 return m_demux && m_demux->is_open() && m_audio && m_audio->is_valid();
187}
188
189// ============================================================================
190// Metadata and Regions
191// ============================================================================
192
193std::optional<FileMetadata> SoundFileReader::get_metadata() const
194{
195 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
196 if (!m_demux || !m_audio)
197 return std::nullopt;
198
199 {
200 std::lock_guard<std::mutex> ml(m_metadata_mutex);
202 return m_cached_metadata;
203 }
204
206
207 std::lock_guard<std::mutex> ml(m_metadata_mutex);
208 return m_cached_metadata;
209}
210
211std::vector<FileRegion> SoundFileReader::get_regions() const
212{
213 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
214 if (!m_demux || !m_audio)
215 return {};
216
217 {
218 std::lock_guard<std::mutex> ml(m_metadata_mutex);
219 if (!m_cached_regions.empty())
220 return m_cached_regions;
221 }
222
224
225 std::lock_guard<std::mutex> ml(m_metadata_mutex);
226 return m_cached_regions;
227}
228
230 const std::shared_ptr<FFmpegDemuxContext>& demux,
231 const std::shared_ptr<AudioStreamContext>& audio) const
232{
233 FileMetadata meta;
234 demux->extract_container_metadata(meta);
235 audio->extract_stream_metadata(*demux, meta);
236
237 meta.file_size = std::filesystem::file_size(m_filepath);
238 auto ftime = std::filesystem::last_write_time(m_filepath);
239 meta.modification_time = std::chrono::system_clock::time_point(
240 std::chrono::seconds(
241 std::chrono::duration_cast<std::chrono::seconds>(ftime.time_since_epoch())));
242
243 std::lock_guard<std::mutex> ml(m_metadata_mutex);
244 m_cached_metadata = std::move(meta);
245}
246
248 const std::shared_ptr<FFmpegDemuxContext>& demux,
249 const std::shared_ptr<AudioStreamContext>& audio) const
250{
251 auto chapters = demux->extract_chapter_regions();
252 auto cues = audio->extract_cue_regions(*demux);
253
254 std::vector<FileRegion> all;
255 all.reserve(chapters.size() + cues.size());
256 all.insert(all.end(), chapters.begin(), chapters.end());
257 all.insert(all.end(), cues.begin(), cues.end());
258
259 std::lock_guard<std::mutex> ml(m_metadata_mutex);
260 m_cached_regions = std::move(all);
261}
262
263// ============================================================================
264// Reading Operations
265// ============================================================================
266
267std::vector<Kakshya::DataVariant> SoundFileReader::read_all()
268{
269 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
270 if (!m_demux || !m_audio) {
271 set_error("File not open");
272 return {};
273 }
274 return decode_frames(m_demux, m_audio, m_audio->total_frames, 0);
275}
276
277std::vector<Kakshya::DataVariant> SoundFileReader::read_region(const FileRegion& region)
278{
279 if (region.start_coordinates.empty() || region.end_coordinates.empty()) {
280 set_error("Invalid region coordinates");
281 return {};
282 }
283 uint64_t start = region.start_coordinates[0];
284 uint64_t end = region.end_coordinates[0];
285 uint64_t n = (end > start) ? (end - start) : 1;
286 return read_frames(n, start);
287}
288
289std::shared_ptr<Kakshya::DynamicSoundStream> SoundFileReader::load_bounded(
290 const std::string& filepath,
291 uint64_t max_frames,
292 bool truncate)
293{
294 if (!can_read(filepath)) {
295 set_error("load_bounded: unsupported file: " + filepath);
296 return nullptr;
297 }
298
299 if (!open(filepath, FileReadOptions::EXTRACT_METADATA)) {
300 set_error("load_bounded: open failed: " + get_last_error());
301 return nullptr;
302 }
303
304 std::shared_ptr<AudioStreamContext> audio;
305 {
306 std::shared_lock lock(m_context_mutex);
307 audio = m_audio;
308 }
309
310 if (!audio || !audio->is_valid()) {
311 set_error("load_bounded: no valid audio stream");
312 close();
313 return nullptr;
314 }
315
316 const uint64_t effective_rate = m_target_sample_rate > 0
318 : audio->sample_rate;
319
320 if (max_frames == 0)
321 max_frames = effective_rate * 5;
322
323 const bool over_limit = audio->total_frames > max_frames;
324
325 if (over_limit && !truncate) {
326 set_error(std::format(
327 "load_bounded: file has {} frames, exceeds limit of {}",
328 audio->total_frames, max_frames));
329 close();
330 return nullptr;
331 }
332
333 if (over_limit) {
335 "load_bounded: {} has {} frames, truncating to {}",
336 filepath, audio->total_frames, max_frames);
337 }
338
339 const uint64_t frames_to_load = over_limit ? max_frames : audio->total_frames;
341
342 auto stream = std::make_shared<Kakshya::DynamicSoundStream>(
343 effective_rate,
344 static_cast<uint32_t>(audio->channels));
345
346 stream->get_structure().organization = planar
349
350 stream->set_auto_resize(false);
351 stream->ensure_capacity(frames_to_load);
352
353 auto data = over_limit
354 ? read_frames(frames_to_load, 0)
355 : read_all();
356
357 close();
358
359 if (data.empty()) {
360 set_error("load_bounded: read returned no data");
361 return nullptr;
362 }
363
364 stream->set_all_data(data);
365
366 return stream;
367}
368
369std::vector<Kakshya::DataVariant> SoundFileReader::read_frames(uint64_t num_frames,
370 uint64_t offset)
371{
372 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
373 if (!m_demux || !m_audio) {
374 set_error("File not open");
375 return {};
376 }
377
378 if (offset != m_current_frame_position.load()) {
379 lock.unlock();
380 std::unique_lock<std::shared_mutex> wlock(m_context_mutex);
381 if (!m_demux || !m_audio) {
382 set_error("File closed during operation");
383 return {};
384 }
385 if (!seek_internal(m_demux, m_audio, offset))
386 return {};
387 wlock.unlock();
388 lock.lock();
389 if (!m_demux || !m_audio) {
390 set_error("File closed during operation");
391 return {};
392 }
393 }
394
395 return decode_frames(m_demux, m_audio, num_frames, offset);
396}
397
398// ============================================================================
399// Seeking
400// ============================================================================
401
402std::vector<uint64_t> SoundFileReader::get_read_position() const
403{
404 return { m_current_frame_position.load() };
405}
406
407bool SoundFileReader::seek(const std::vector<uint64_t>& position)
408{
409 if (position.empty()) {
410 set_error("Empty position vector");
411 return false;
412 }
413 std::unique_lock<std::shared_mutex> lock(m_context_mutex);
414 if (!m_demux || !m_audio) {
415 set_error("File not open");
416 return false;
417 }
419}
420
422 const std::shared_ptr<FFmpegDemuxContext>& demux,
423 const std::shared_ptr<AudioStreamContext>& audio,
424 uint64_t frame_position)
425{
426 if (frame_position > audio->total_frames)
427 frame_position = audio->total_frames;
428
429 if (audio->sample_rate == 0) {
430 set_error("Invalid sample rate");
431 return false;
432 }
433
434 AVStream* stream = demux->get_stream(audio->stream_index);
435 if (!stream) {
436 set_error("Invalid stream index");
437 return false;
438 }
439
440 int64_t ts = av_rescale_q(
441 static_cast<int64_t>(frame_position),
442 AVRational { .num = 1, .den = static_cast<int>(audio->sample_rate) },
443 stream->time_base);
444
445 if (!demux->seek(audio->stream_index, ts)) {
446 set_error(demux->last_error());
447 return false;
448 }
449
450 audio->flush_codec();
451 audio->drain_resampler_init();
452 m_current_frame_position = frame_position;
453 return true;
454}
455
456// =========================================================================
457// Decode loop
458// =========================================================================
459
460std::vector<Kakshya::DataVariant> SoundFileReader::decode_frames(
461 const std::shared_ptr<FFmpegDemuxContext>& demux,
462 const std::shared_ptr<AudioStreamContext>& audio,
463 uint64_t num_frames,
464 uint64_t /*offset*/)
465{
466 if (!audio->is_valid()) {
467 set_error("Invalid audio context for decoding");
468 return {};
469 }
470
472 int ch = static_cast<int>(audio->channels);
473
474 std::vector<Kakshya::DataVariant> output;
475 if (use_planar) {
476 output.resize(ch);
477 for (auto& v : output) {
478 v = std::vector<double>();
479 std::get<std::vector<double>>(v).reserve(num_frames);
480 }
481 } else {
482 output.resize(1);
483 output[0] = std::vector<double>();
484 std::get<std::vector<double>>(output[0]).reserve(num_frames * static_cast<size_t>(ch));
485 }
486
487 uint64_t decoded = 0;
488 bool eof_reached = false;
489
490 AVPacket* pkt = av_packet_alloc();
491 AVFrame* frame = av_frame_alloc();
492 if (!pkt || !frame) {
493 av_packet_free(&pkt);
494 av_frame_free(&frame);
495 set_error("Failed to allocate packet/frame");
496 return {};
497 }
498
499 uint32_t out_rate = m_target_sample_rate > 0 ? m_target_sample_rate : audio->sample_rate;
500 int max_resampled = static_cast<int>(av_rescale_rnd(
501 static_cast<int64_t>(num_frames), out_rate, audio->sample_rate, AV_ROUND_UP));
502
503 AVSampleFormat tgt_fmt = use_planar ? AV_SAMPLE_FMT_DBLP : AV_SAMPLE_FMT_DBL;
504 uint8_t** resample_buf = nullptr;
505 int linesize = 0;
506
507 if (av_samples_alloc_array_and_samples(
508 &resample_buf, &linesize, ch, max_resampled, tgt_fmt, 0)
509 < 0) {
510 av_packet_free(&pkt);
511 av_frame_free(&frame);
512 set_error("Failed to allocate resample buffer");
513 return {};
514 }
515
516 while (decoded < num_frames) {
517 if (!eof_reached) {
518 int ret = av_read_frame(demux->format_context, pkt);
519 if (ret == AVERROR_EOF) {
520 eof_reached = true;
521 avcodec_send_packet(audio->codec_context, nullptr);
522 } else if (ret < 0) {
523 eof_reached = true;
524 } else if (pkt->stream_index == audio->stream_index) {
525 avcodec_send_packet(audio->codec_context, pkt);
526 av_packet_unref(pkt);
527 } else {
528 av_packet_unref(pkt);
529 }
530 }
531
532 int receive_ret = 0;
533 while (decoded < num_frames) {
534 receive_ret = avcodec_receive_frame(audio->codec_context, frame);
535
536 if (receive_ret == AVERROR(EAGAIN))
537 break;
538 if (receive_ret == AVERROR_EOF) {
539 // decoded = num_frames;
540 break;
541 }
542 if (receive_ret < 0)
543 break;
544
545 int out_samples = swr_convert(
546 audio->swr_context,
547 resample_buf, max_resampled,
548 const_cast<const uint8_t**>(frame->data),
549 frame->nb_samples);
550
551 if (out_samples > 0) {
552 uint64_t to_copy = std::min(static_cast<uint64_t>(out_samples),
553 num_frames - decoded);
554 if (use_planar) {
555 for (int c = 0; c < ch; ++c) {
556 auto* src = reinterpret_cast<double*>(resample_buf[c]);
557 auto& dst = std::get<std::vector<double>>(output[c]);
558 dst.insert(dst.end(), src, src + to_copy);
559 }
560 } else {
561 auto* src = reinterpret_cast<double*>(resample_buf[0]);
562 auto& dst = std::get<std::vector<double>>(output[0]);
563 dst.insert(dst.end(), src, src + to_copy * static_cast<uint64_t>(ch));
564 }
565 decoded += to_copy;
566 }
567 av_frame_unref(frame);
568 }
569
570 if (eof_reached && receive_ret == AVERROR_EOF)
571 break;
572 }
573
574 while (true) {
575 int n = swr_convert(audio->swr_context, resample_buf, max_resampled, nullptr, 0);
576 if (n <= 0)
577 break;
578
579 uint64_t to_copy = std::min(static_cast<uint64_t>(n),
580 (num_frames > decoded) ? (num_frames - decoded) : 0);
581
582 if (to_copy > 0) {
583 if (use_planar) {
584 for (int c = 0; c < ch; ++c) {
585 auto* src = reinterpret_cast<double*>(resample_buf[c]);
586 auto& dst = std::get<std::vector<double>>(output[c]);
587 dst.insert(dst.end(), src, src + to_copy);
588 }
589 } else {
590 auto* src = reinterpret_cast<double*>(resample_buf[0]);
591 auto& dst = std::get<std::vector<double>>(output[0]);
592 dst.insert(dst.end(), src, src + to_copy * static_cast<uint64_t>(ch));
593 }
594 decoded += to_copy;
595 } else {
596 break;
597 }
598 }
599
600 av_freep(&resample_buf[0]);
601 av_freep(&resample_buf);
602 av_packet_free(&pkt);
603 av_frame_free(&frame);
604
605 m_current_frame_position += decoded;
606 return output;
607}
608
609// =========================================================================
610// Container Operations
611// =========================================================================
612
613std::shared_ptr<Kakshya::SignalSourceContainer> SoundFileReader::create_container()
614{
615 std::shared_lock lock(m_context_mutex);
616 if (!m_demux || !m_audio) {
617 set_error("File not open");
618 return nullptr;
619 }
620
621 return std::make_shared<Kakshya::SoundFileContainer>();
622}
623
625 std::shared_ptr<Kakshya::SignalSourceContainer> container)
626{
627 if (!container) {
628 set_error("Invalid container");
629 return false;
630 }
631
632 auto sc = std::dynamic_pointer_cast<Kakshya::SoundFileContainer>(container);
633 if (!sc) {
634 set_error("Container is not a SoundFileContainer");
635 return false;
636 }
637
638 std::shared_ptr<AudioStreamContext> audio;
639 {
640 std::shared_lock lock(m_context_mutex);
641 if (!m_demux || !m_audio) {
642 set_error("File not open");
643 return false;
644 }
645 audio = m_audio;
646 }
647
648 sc->set_source_path(m_filepath);
649 if (m_demux && m_demux->format_context)
650 sc->set_source_format(m_demux->format_context->iformat->name);
651
652 sc->setup(audio->total_frames, audio->sample_rate, audio->channels);
653
655 sc->get_structure().organization = planar
658
659 auto data = read_all();
660 if (data.empty()) {
661 set_error("Failed to read audio data");
662 return false;
663 }
664
665 sc->set_raw_data(data);
666
667 auto regions = get_regions();
668 auto region_groups = regions_to_groups(regions);
669 for (const auto& [name, group] : region_groups)
670 sc->add_region_group(group);
671
672 sc->create_default_processor();
673 sc->mark_ready_for_processing(true);
674 return true;
675}
676
677// ============================================================================
678// Utility Methods
679// ============================================================================
680
682{
683 return 2; // time × channels
684}
685
686std::vector<uint64_t> SoundFileReader::get_dimension_sizes() const
687{
688 std::shared_lock<std::shared_mutex> lock(m_context_mutex);
689 if (!m_audio)
690 return { 0, 0 };
691 return { m_audio->total_frames, m_audio->channels };
692}
693
694std::vector<std::string> SoundFileReader::get_supported_extensions() const
695{
696 return {
697 "wav", "flac", "mp3", "m4a", "aac", "ogg", "opus", "wma",
698 "aiff", "aif", "ape", "wv", "tta", "mka", "ac3", "dts",
699 "mp2", "mp4", "webm", "caf", "amr", "au", "voc", "w64",
700 "mpc", "mp+", "m4b", "m4r", "3gp", "3g2", "asf", "rm",
701 "ra", "avi", "mov", "mkv", "ogv", "ogx", "oga", "spx",
702 "f4a", "f4b", "f4v", "m4v", "asx", "wvx", "wax"
703 };
704}
705
707{
708 std::lock_guard<std::mutex> lock(m_error_mutex);
709 return m_last_error;
710}
711
712void SoundFileReader::set_error(const std::string& err) const
713{
714 std::lock_guard<std::mutex> lock(m_error_mutex);
715 m_last_error = err;
716 MF_ERROR(Journal::Component::IO, Journal::Context::FileIO, "SoundFileReader: {}", err);
717}
718
720{
721 std::lock_guard<std::mutex> lock(m_error_mutex);
722 m_last_error.clear();
723}
724
725} // namespace MayaFlux::IO
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
glm::vec3 position
bool open(const std::string &filepath)
Open a media file and probe stream information.
static void init_ffmpeg()
Initialise FFmpeg logging level once per process.
int find_best_stream(int media_type, const void **out_codec=nullptr) const
Find the best stream of the requested media type.
RAII owner of a single AVFormatContext and associated demux state.
static std::string resolve_path(const std::string &filepath)
Resolve a filepath against the project source root if not found as-is.
static std::unordered_map< std::string, Kakshya::RegionGroup > regions_to_groups(const std::vector< FileRegion > &regions)
Convert file regions to region groups.
std::vector< Kakshya::DataVariant > read_all() override
Read the entire audio file into memory.
void close() override
Close the currently open file and release resources.
void build_regions(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< AudioStreamContext > &audio) const
Build and cache FileRegion list from both contexts.
std::string get_last_error() const override
Get the last error message encountered by the reader.
uint32_t m_target_sample_rate
Target sample rate for resampling (0 = use source rate).
bool open(const std::string &filepath, FileReadOptions options=FileReadOptions::ALL) override
Open an audio file for reading.
std::mutex m_error_mutex
Mutex for thread-safe error message access.
void build_metadata(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< AudioStreamContext > &audio) const
Build and cache FileMetadata from both contexts.
std::shared_ptr< AudioStreamContext > m_audio
Codec + resampler state.
bool load_into_container(std::shared_ptr< Kakshya::SignalSourceContainer > container) override
Load file data into an existing SignalSourceContainer.
std::shared_mutex m_context_mutex
Guards both context pointers.
std::shared_ptr< Kakshya::DynamicSoundStream > load_bounded(const std::string &filepath, uint64_t max_frames=0, bool truncate=false)
Load an audio file into a size-bounded DynamicSoundStream.
bool seek_internal(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< AudioStreamContext > &audio, uint64_t frame_position)
Seek the demuxer and flush the codec to the given frame position.
bool can_read(const std::string &filepath) const override
Check if this reader can open the given file.
std::mutex m_metadata_mutex
Mutex for thread-safe metadata access.
std::vector< Kakshya::DataVariant > decode_frames(const std::shared_ptr< FFmpegDemuxContext > &demux, const std::shared_ptr< AudioStreamContext > &audio, uint64_t num_frames, uint64_t offset)
Decode num_frames PCM frames starting at offset.
std::string m_last_error
Last error message encountered.
std::vector< uint64_t > get_read_position() const override
Get the current read position in the file.
void set_error(const std::string &error) const
Set the last error message.
std::atomic< uint64_t > m_current_frame_position
Current frame position for reading.
std::vector< Kakshya::DataVariant > read_region(const FileRegion &region) override
Read a specific region from the file.
std::vector< Kakshya::DataVariant > read_frames(uint64_t num_frames, uint64_t offset=0)
Read a specific number of frames from the file.
AudioReadOptions m_audio_options
Audio-specific read options.
std::optional< FileMetadata > m_cached_metadata
Cached file metadata.
bool open_from_demux(std::shared_ptr< FFmpegDemuxContext > demux, std::shared_ptr< AudioStreamContext > audio, const std::string &filepath, FileReadOptions options=FileReadOptions::ALL)
Open an audio stream from an already-constructed demux and stream context.
std::vector< FileRegion > m_cached_regions
Cached file regions (markers, loops, etc.).
void clear_error() const
Clear the last error message.
~SoundFileReader() override
Destroy the SoundFileReader object.
std::string m_filepath
Path to the currently open file.
std::vector< uint64_t > get_dimension_sizes() const override
Get the size of each dimension (e.g., frames, channels).
std::shared_ptr< Kakshya::SignalSourceContainer > create_container() override
Create a SignalSourceContainer for this file.
bool seek(const std::vector< uint64_t > &position) override
Seek to a specific position in the file.
SoundFileReader()
Construct a new SoundFileReader object.
size_t get_num_dimensions() const override
Get the number of dimensions in the audio data (typically 2: time, channel).
std::shared_ptr< FFmpegDemuxContext > m_demux
Container / format state.
std::optional< FileMetadata > get_metadata() const override
Get metadata for the currently open file.
std::vector< FileRegion > get_regions() const override
Get all regions (markers, loops, etc.) from the file.
bool is_open() const override
Check if a file is currently open.
FileReadOptions m_options
File read options used for this session.
std::vector< std::string > get_supported_extensions() const override
Get supported file extensions for this reader.
@ DEINTERLEAVE
Output planar (per-channel) doubles instead of interleaved.
FileReadOptions
Generic options for file reading behavior.
@ EXTRACT_METADATA
Extract file metadata.
@ EXTRACT_REGIONS
Extract semantic regions (format-specific)
@ NONE
No special options.
@ FileIO
Filesystem I/O operations.
@ IO
Networking, file handling, streaming.
@ PLANAR
Separate DataVariant per logical unit (LLL...RRR for stereo)
@ INTERLEAVED
Single DataVariant with interleaved data (LRLRLR for stereo)
uint64_t file_size
Size in bytes.
std::chrono::system_clock::time_point modification_time
Last modification time.
Generic metadata structure for any file type.
std::vector< uint64_t > start_coordinates
N-dimensional start position (e.g., frame, x, y)
Kakshya::Region to_region() const
Convert this FileRegion to a Region for use in processing.
std::string name
Human-readable name for the region.
std::string type
Region type identifier (e.g., "cue", "scene", "block")
std::unordered_map< std::string, std::any > attributes
Region-specific metadata.
std::vector< uint64_t > end_coordinates
N-dimensional end position (inclusive)
Generic region descriptor for any file type.
static Region time_span(uint64_t start_frame, uint64_t end_frame, const std::string &label="", const std::any &extra_data={})
Create a Region representing a time span (e.g., a segment of frames).
Definition Region.hpp:135
void set_attribute(const std::string &key, std::any value)
Set an attribute value by key.
Definition Region.hpp:339
static Region time_point(uint64_t frame, const std::string &label="", const std::any &extra_data={})
Create a Region representing a single time point (e.g., a frame or sample).
Definition Region.hpp:114
Represents a point or span in N-dimensional space.
Definition Region.hpp:67