diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/cue_splitter.c | 192 | ||||
| -rw-r--r-- | tests/meson.build | 4 |
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) |