vgmstream/cli/vgmstream_cli_utils.c

294 lines
9.2 KiB
C
Raw Normal View History

2024-07-28 17:29:43 +02:00
#include <string.h>
#include <inttypes.h>
#include <stdio.h>
2024-07-25 17:15:49 +02:00
2024-07-28 17:29:43 +02:00
#include "vgmstream_cli.h"
2024-07-25 17:15:49 +02:00
#include "../src/api.h"
#include "../src/vgmstream.h"
2024-07-28 18:24:40 +02:00
#include "vjson.h"
2024-07-25 17:15:49 +02:00
static void clean_filename(char* dst, int clean_paths) {
for (int i = 0; i < strlen(dst); i++) {
char c = dst[i];
int is_badchar = (clean_paths && (c == '\\' || c == '/'))
|| c == '*' || c == '?' || c == ':' /*|| c == '|'*/ || c == '<' || c == '>';
if (is_badchar)
dst[i] = '_';
}
}
/* replaces a filename with "?n" (stream name), "?f" (infilename) or "?s" (subsong) wildcards
* ("?" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */
void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* vgmstream) {
int subsong;
2024-07-28 17:29:43 +02:00
char stream_name[CLI_PATH_LIMIT];
char buf[CLI_PATH_LIMIT];
char tmp[CLI_PATH_LIMIT];
2024-07-25 17:15:49 +02:00
/* file has a "%" > temp replace for sprintf */
strcpy(buf, cfg->outfilename_config);
for (int i = 0; i < strlen(buf); i++) {
if (buf[i] == '%')
buf[i] = '|'; /* non-valid filename, not used in format */
}
/* init config */
subsong = vgmstream->stream_index;
if (subsong > vgmstream->num_streams || subsong != cfg->subsong_index) {
subsong = 0; /* for games without subsongs / bad config */
}
if (vgmstream->stream_name[0] != '\0') {
snprintf(stream_name, sizeof(stream_name), "%s", vgmstream->stream_name);
clean_filename(stream_name, 1); /* clean subsong name's subdirs */
}
else {
snprintf(stream_name, sizeof(stream_name), "%s", cfg->infilename);
clean_filename(stream_name, 0); /* don't clean user's subdirs */
}
/* do controlled replaces of each wildcard (in theory could appear N times) */
do {
char* pos = strchr(buf, '?');
if (!pos)
break;
/* use buf as format and copy formatted result to tmp (assuming sprintf's format must not overlap with dst) */
if (pos[1] == 'n') {
pos[0] = '%';
pos[1] = 's'; /* use %s */
snprintf(tmp, sizeof(tmp), buf, stream_name);
}
else if (pos[1] == 'f') {
pos[0] = '%';
pos[1] = 's'; /* use %s */
snprintf(tmp, sizeof(tmp), buf, cfg->infilename);
}
else if (pos[1] == 's') {
pos[0] = '%';
pos[1] = 'i'; /* use %i */
snprintf(tmp, sizeof(tmp), buf, subsong);
}
else if ((pos[1] == '0' && pos[2] >= '1' && pos[2] <= '9' && pos[3] == 's')) {
pos[0] = '%';
pos[3] = 'i'; /* use %0Ni */
snprintf(tmp, sizeof(tmp), buf, subsong);
}
else {
/* not recognized */
continue;
}
/* copy result to buf again, so it can be used as format in next replace
* (can be optimized with some pointer swapping but who cares about a few extra nanoseconds) */
strcpy(buf, tmp);
}
while (1);
/* replace % back */
for (int i = 0; i < strlen(buf); i++) {
if (buf[i] == '|')
buf[i] = '%';
}
snprintf(dst, dstsize, "%s", buf);
}
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) {
int channels = vgmstream->channels;
2024-07-28 17:29:43 +02:00
int64_t num_samples = vgmstream->num_samples;
bool loop_flag = vgmstream->loop_flag;
int64_t loop_start = vgmstream->loop_start_sample;
int64_t loop_end = vgmstream->loop_start_sample;
2024-07-25 17:15:49 +02:00
if (!cfg->play_sdtout) {
if (cfg->print_adxencd) {
printf("adxencd");
if (!cfg->print_metaonly)
2024-07-28 17:29:43 +02:00
printf(" \"%s\"", cfg->outfilename);
if (loop_flag)
printf(" -lps%"PRId64" -lpe%"PRId64, loop_start, loop_end);
2024-07-25 17:15:49 +02:00
printf("\n");
}
else if (cfg->print_oggenc) {
printf("oggenc");
if (!cfg->print_metaonly)
printf(" \"%s\"", cfg->outfilename);
2024-07-28 17:29:43 +02:00
if (loop_flag)
printf(" -c LOOPSTART=%"PRId64" -c LOOPLENGTH=%"PRId64, loop_start, loop_end - loop_start);
2024-07-25 17:15:49 +02:00
printf("\n");
}
else if (cfg->print_batchvar) {
if (!cfg->print_metaonly)
printf("set fname=\"%s\"\n", cfg->outfilename);
2024-07-28 17:29:43 +02:00
printf("set tsamp=%"PRId64"\nset chan=%d\n", num_samples, channels);
if (loop_flag)
printf("set lstart=%"PRId64"\nset lend=%"PRId64"\nset loop=1\n", loop_start, loop_end);
2024-07-25 17:15:49 +02:00
else
printf("set loop=0\n");
}
else if (cfg->print_metaonly) {
printf("metadata for %s\n", cfg->infilename);
}
else {
printf("decoding %s\n", cfg->infilename);
}
}
if (!cfg->play_sdtout && !cfg->print_adxencd && !cfg->print_oggenc && !cfg->print_batchvar) {
char description[1024];
describe_vgmstream(vgmstream, description, 1024);
printf("%s", description);
}
}
void print_tags(cli_config_t* cfg) {
VGMSTREAM_TAGS* tags = NULL;
STREAMFILE* sf_tags = NULL;
const char *tag_key, *tag_val;
if (!cfg->tag_filename)
return;
sf_tags = open_stdio_streamfile(cfg->tag_filename);
if (!sf_tags) {
printf("tag file %s not found\n", cfg->tag_filename);
return;
}
printf("tags:\n");
tags = vgmstream_tags_init(&tag_key, &tag_val);
vgmstream_tags_reset(tags, cfg->infilename);
while (vgmstream_tags_next_tag(tags, sf_tags)) {
printf("- '%s'='%s'\n", tag_key, tag_val);
}
vgmstream_tags_close(tags);
close_streamfile(sf_tags);
}
void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg) {
char title[1024];
vgmstream_title_t tcfg = {0};
if (!cfg->print_title)
return;
tcfg.force_title = 0;
tcfg.subsong_range = 0;
tcfg.remove_extension = 0;
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, &tcfg);
printf("title: %s\n", title);
}
2024-07-25 19:10:46 +02:00
void print_json_version(const char* vgmstream_version) {
2024-07-28 18:24:40 +02:00
size_t extension_list_len = 0;
2024-07-25 17:15:49 +02:00
const char** extension_list;
2024-07-28 18:24:40 +02:00
vjson_t j = {0};
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
char buf[0x4000]; // exts need ~0x1400
vjson_init(&j, buf, sizeof(buf));
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_obj_open(&j);
vjson_keystr(&j, "version", vgmstream_version);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "extensions");
vjson_obj_open(&j);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "vgm");
vjson_arr_open(&j);
extension_list = vgmstream_get_formats(&extension_list_len);
for (int i = 0; i < extension_list_len; i++) {
vjson_str(&j, extension_list[i]);
}
vjson_arr_close(&j);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "common");
vjson_arr_open(&j);
extension_list = vgmstream_get_common_formats(&extension_list_len);
for (int i = 0; i < extension_list_len; i++) {
vjson_str(&j, extension_list[i]);
}
vjson_arr_close(&j);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_obj_close(&j);
vjson_obj_close(&j);
printf("%s\n", buf);
2024-07-25 17:15:49 +02:00
}
2024-07-25 19:10:46 +02:00
void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version) {
2024-07-28 18:24:40 +02:00
char buf[0x1000]; // probably fine with ~0x400
vjson_t j = {0};
vjson_init(&j, buf, sizeof(buf));
2024-07-25 17:15:49 +02:00
vgmstream_info info;
describe_vgmstream_info(vgm, &info);
2024-07-28 18:24:40 +02:00
vjson_obj_open(&j);
vjson_keystr(&j, "version", vgmstream_version);
vjson_keyint(&j, "sampleRate", info.sample_rate);
vjson_keyint(&j, "channels", info.channels);
vjson_key(&j, "mixingInfo");
if (info.mixing_info.input_channels > 0) {
vjson_obj_open(&j);
vjson_keyint(&j, "inputChannels", info.mixing_info.input_channels);
vjson_keyint(&j, "outputChannels", info.mixing_info.output_channels);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_keyintnull(&j, "channelLayout", info.channel_layout);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "loopingInfo");
if (info.loop_info.end > info.loop_info.start) {
vjson_obj_open(&j);
vjson_keyint(&j, "start", info.loop_info.start);
vjson_keyint(&j, "end", info.loop_info.start);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "interleaveInfo");
if (info.interleave_info.last_block > info.interleave_info.first_block) {
vjson_obj_open(&j);
vjson_keyint(&j, "firstBlock", info.interleave_info.last_block);
vjson_keyint(&j, "lastBlock", info.interleave_info.first_block);
vjson_obj_close(&j);
}
else {
vjson_null(&j);
}
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_keyint(&j, "numberOfSamples", info.num_samples);
vjson_keystr(&j, "encoding", info.encoding);
vjson_keystr(&j, "layout", info.layout);
vjson_keyintnull(&j, "frameSize", info.frame_size);
vjson_keystr(&j, "metadataSource", info.metadata);
vjson_keyint(&j, "bitrate", info.bitrate);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_key(&j, "streamInfo");
vjson_obj_open(&j);
vjson_keyint(&j, "index", info.stream_info.current);
vjson_keystr(&j, "name", info.stream_info.name);
vjson_keyint(&j, "total", info.stream_info.total);
vjson_obj_close(&j);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
vjson_obj_close(&j);
2024-07-25 17:15:49 +02:00
2024-07-28 18:24:40 +02:00
printf("%s\n", buf);
2024-07-25 17:15:49 +02:00
}