summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2025-05-04 14:53:21 -0400
committerAndrew Opalach <andrew@akon.city> 2025-05-04 14:53:21 -0400
commitefbe4b52a22ea3c4458401a7acbce89a5bf29299 (patch)
tree0f2935024ed1b9166f739f9ed2222da485077316 /tests
parent2ce402fa41508d45e2d30b56502c00ef92efff7b (diff)
downloadcamu-efbe4b52a22ea3c4458401a7acbce89a5bf29299.tar.gz
camu-efbe4b52a22ea3c4458401a7acbce89a5bf29299.tar.bz2
camu-efbe4b52a22ea3c4458401a7acbce89a5bf29299.zip
Prepare for cch_handle_write(), cue testing
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'tests')
-rw-r--r--tests/cue_splitter.c192
-rw-r--r--tests/meson.build4
2 files changed, 196 insertions, 0 deletions
diff --git a/tests/cue_splitter.c b/tests/cue_splitter.c
new file mode 100644
index 0000000..f34ec83
--- /dev/null
+++ b/tests/cue_splitter.c
@@ -0,0 +1,192 @@
+#define AL_LOG_SECTION "cue_splitter"
+#include <al/log.h>
+#include <al/lib.h>
+#include <nnwt/file.h>
+#include <libcue.h>
+
+#include "../src/codec/codec.h"
+#include "../src/codec/ffmpeg/demuxer.h"
+#include "../src/codec/ffmpeg/decoder.h"
+#include "../src/codec/ffmpeg/encoder.h"
+
+struct worker_info {
+ str filename;
+ off_t start;
+ off_t length;
+ struct cch_handle handle;
+ struct camu_demuxer *demux;
+ struct camu_decoder *dec;
+ struct camu_encoder *enc;
+ struct cch_entry *output;
+};
+
+static void data_callback(void *userdata, struct camu_codec_frame *frame)
+{
+ struct worker_info *info = (struct worker_info *)userdata;
+ (void)frame;
+}
+
+static nn_thread_result NNWT_THREADCALL worker_thread(void *userdata)
+{
+ struct worker_info *info = (struct worker_info *)userdata;
+
+ if (!info->demux->init(info->demux, &info->handle)) {
+ goto err;
+ }
+ // @TOOD: Check AVFMT_NO_BYTE_SEEK.
+
+ struct camu_codec_stream *stream;
+ al_array_foreach_ptr(info->demux->streams, i, stream) {
+ if (stream->type == CAMU_STREAM_AUDIO) {
+ info->demux->subscribed = 1 << stream->index;
+ break;
+ }
+ }
+ if (info->demux->subscribed == 0) {
+ log_error("File has no audio stream.");
+ goto err;
+ }
+
+ if (!info->dec->init(info->dec, NULL, stream, data_callback, info)) {
+ goto err;
+ }
+
+ AVPacket *pkt = av_packet_alloc();
+ struct camu_codec_packet packet = { .av.pkt = pkt };
+ s32 status;
+ do {
+ status = info->demux->get_packet(info->demux, &packet);
+ if (status == CAMU_OK) {
+ info->dec->push_av_packet(info->dec, pkt);
+ info->dec->process(info->dec);
+ av_packet_unref(pkt);
+ }
+ } while (status == CAMU_OK);
+ info->dec->flush(info->dec);
+
+ return 0;
+err:
+ log_error("Worker \"%.*s\" failed.", al_str_x(&info->filename));
+ return 0;
+}
+
+static void dump_cdtext(Cdtext *text)
+{
+ al_printf(" CDTEXT:\n");
+ if (cdtext_get(PTI_TITLE, text)) al_printf(" TITLE: %s\n", cdtext_get(PTI_TITLE, text));
+ if (cdtext_get(PTI_PERFORMER, text)) al_printf(" PERFORMER: %s\n", cdtext_get(PTI_PERFORMER, text));
+ if (cdtext_get(PTI_SONGWRITER, text)) al_printf(" SONGWRITER: %s\n", cdtext_get(PTI_SONGWRITER, text));
+ if (cdtext_get(PTI_COMPOSER, text)) al_printf(" COMPOSER: %s\n", cdtext_get(PTI_COMPOSER, text));
+ if (cdtext_get(PTI_ARRANGER, text)) al_printf(" ARRANGER: %s\n", cdtext_get(PTI_ARRANGER, text));
+ if (cdtext_get(PTI_MESSAGE, text)) al_printf(" MESSAGE: %s\n", cdtext_get(PTI_MESSAGE, text));
+ if (cdtext_get(PTI_DISC_ID, text)) al_printf(" DISC_ID: %s\n", cdtext_get(PTI_DISC_ID, text));
+ if (cdtext_get(PTI_GENRE, text)) al_printf(" GENRE: %s\n", cdtext_get(PTI_GENRE, text));
+}
+
+static void dump_rem(Rem *rem, bool track)
+{
+ if (!(rem_get(REM_DATE, rem) ||
+ (track && (rem_get(REM_REPLAYGAIN_TRACK_GAIN, rem) || rem_get(REM_REPLAYGAIN_TRACK_GAIN, rem))) ||
+ (!track && (rem_get(REM_REPLAYGAIN_ALBUM_GAIN, rem) || rem_get(REM_REPLAYGAIN_ALBUM_GAIN, rem))))) {
+ return;
+ }
+ al_printf(" REM:\n");
+ if (rem_get(REM_DATE, rem)) {
+ al_printf(" DATE: %s\n", rem_get(REM_DATE, rem));
+ }
+ if (track) {
+ if (rem_get(REM_REPLAYGAIN_TRACK_GAIN, rem)) {
+ al_printf(" REPLAYGAIN_TRACK_GAIN: %s\n", rem_get(REM_REPLAYGAIN_TRACK_GAIN, rem));
+ }
+ if (rem_get(REM_REPLAYGAIN_TRACK_PEAK, rem)) {
+ al_printf(" REPLAYGAIN_TRACK_GAIN: %s\n", rem_get(REM_REPLAYGAIN_TRACK_PEAK, rem));
+ }
+ } else {
+ if (rem_get(REM_REPLAYGAIN_ALBUM_GAIN, rem)) {
+ al_printf(" REPLAYGAIN_ALBUM_GAIN: %s\n", rem_get(REM_REPLAYGAIN_ALBUM_GAIN, rem));
+ }
+ if (rem_get(REM_REPLAYGAIN_ALBUM_PEAK, rem)) {
+ al_printf(" REPLAYGAIN_ALBUM_GAIN: %s\n", rem_get(REM_REPLAYGAIN_ALBUM_PEAK, rem));
+ }
+ }
+}
+
+s32 main(s32 argc, char *argv[])
+{
+ if (argc < 2 || argc > 3) {
+ al_printf("Usage: %s [sheet.cue] <output_dir> <comma,separated,track,numbers>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ str input_path = al_str_cr(argv[1]);
+ struct nn_file input;
+ if (!nn_file_open(&input, &input_path, NNWT_FILE_READONLY)) {
+ al_printf("File \%.*s\" doesn't exist.\n", al_str_x(&input_path));
+ return EXIT_FAILURE;
+ }
+
+ str output_dir = al_str_null();
+ if (argc == 3) {
+ output_dir = al_str_cr(argv[2]);
+ if (!nn_dir_exists(&output_dir)) {
+ if (!nn_dir_create(&output_dir)) {
+ al_printf("Directory \"%.*s\" doesn't exist and failed to be created.\n", al_str_x(&output_dir));
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ char *cue_string;
+ nn_file_read_as_c_str(&input, &cue_string);
+ Cd *cue = cue_parse_string(cue_string);
+ s32 tracks = cd_get_ntrack(cue);
+ al_printf("Cue sheet loaded with %d tracks.\n", tracks);
+ Cdtext *text = cd_get_cdtext(cue);
+ if (text) dump_cdtext(text);
+ Rem *rem = cd_get_rem(cue);
+ if (rem) dump_rem(rem, false);
+ for (s32 i = 1; i <= tracks; i++) {
+ Track *track = cd_get_track(cue, i);
+ al_printf("Filename: %s\n", track_get_filename(track));
+ al_printf(" Start: %ld\n", track_get_start(track));
+ al_printf(" Length: %ld\n", track_get_length(track));
+ al_printf(" Pre-gap: %ld\n", track_get_zero_pre(track));
+ al_printf(" Post-gap: %ld\n", track_get_zero_post(track));
+ al_printf(" Mode: %d\n", track_get_mode(track));
+ al_printf(" SubMode: %d\n", track_get_sub_mode(track));
+ if (track_get_isrc(track)) {
+ al_printf(" ISRC: %s\n", track_get_isrc(track));
+ }
+ text = track_get_cdtext(track);
+ if (text) dump_cdtext(text);
+ Rem *rem = track_get_rem(track);
+ if (rem) dump_rem(rem, true);
+ }
+
+ // One cache entry to file/files, multiple handles shouldn't be an issue.
+ // Per thread: get_handle() -> demuxer -> encoder -> muxer
+ if (output_dir.length != 0) {
+ struct worker_info *workers = al_malloc(sizeof(struct worker_info) * tracks);
+ for (s32 i = 1; i <= tracks; i++) {
+ struct worker_info *info = &workers[i - 1];
+ Track *track = cd_get_track(cue, i);
+ Cdtext *text = track_get_cdtext(track);
+ const char *title = cdtext_get(PTI_TITLE, text);
+ u32 id_len = al_strlen(title) + 11;
+ al_str_sized(&info->filename, id_len);
+ al_snprintf(info->filename.data, id_len, "%02d - %s.flac", i, title);
+ // We need to allocate an extra byte when working with snprintf.
+ info->filename.length = id_len - 1;
+ str output_path;
+ al_str_clone(&output_path, &output_dir);
+ al_str_cat(&output_path, &al_str_c("/"));
+ al_str_cat(&output_path, &info->filename);
+ // start and length from libcue ignore all gaps.
+ info->start = track_get_start(track);
+ info->length = track_get_length(track);
+ al_printf("start: %zd, length: %zd, %.*s\n", info->start, info->length, al_str_x(&output_path));
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 0000000..191ccc1
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,4 @@
+cue_splitter_src = ['cue_splitter.c']
+libcue = dependency('libcue')
+cue_splitter_deps = [common_deps, cache, codecs_server, codecs_client, libcue]
+executable('cue_splitter', cue_splitter_src, dependencies: cue_splitter_deps, install: false)