#define AL_LOG_SECTION "cue_splitter" #include #include #include #include #ifdef CAMU_HAVE_FFMPEG #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)info; (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; } // @TODO: 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] \n", argv[0]); return EXIT_FAILURE; } (void)worker_thread; 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 = 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); 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 ignores 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; } #else s32 main(void) { return EXIT_FAILURE; } #endif