mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 00:20:47 +01:00
commit
9206295e09
135
cli/txtp_segmenter.py
Normal file
135
cli/txtp_segmenter.py
Normal file
@ -0,0 +1,135 @@
|
||||
# !/usr/bin/python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import argparse
|
||||
import re
|
||||
|
||||
|
||||
def parse():
|
||||
description = (
|
||||
"creates segmented .txtp from a list of files obtained using wildcards"
|
||||
)
|
||||
epilog = (
|
||||
"examples:\n"
|
||||
"%(prog)s bgm_*.ogg\n"
|
||||
"- get all files that start with bgm_ and end with .ogg\n"
|
||||
"%(prog)s bgm_??.* -n bgm_main.txtp -cls 2\n"
|
||||
"- get all files that start with bgm_ and end with 2 chars plus any extension\n"
|
||||
"%(prog)s files/bgm_*_all.ogg -s\n"
|
||||
"- create single .txtp per every bgm_(something)_all.ogg inside files dir\n"
|
||||
"%(prog)s **/*.ogg -l\n"
|
||||
"- find all .ogg in all subdirs but list only"
|
||||
"%(prog)s files/*.ogg -f .+(a|all)[.]ogg$\n"
|
||||
"- find all .ogg in files except those that end with 'a.ogg' or 'all.ogg'\n"
|
||||
)
|
||||
|
||||
parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter)
|
||||
parser.add_argument("files", help="files to match")
|
||||
parser.add_argument("-n","--name", help="generated txtp name (adapts 'files' by default)")
|
||||
parser.add_argument("-f","--filter", help="filter matched files with regex")
|
||||
parser.add_argument("-s","--single", help="generate single files per list match", action='store_true')
|
||||
parser.add_argument("-l","--list", help="list only results and don't write", action='store_true')
|
||||
parser.add_argument("-cls","--command-loop-start", help="sets loop start segment")
|
||||
parser.add_argument("-cle","--command-loop-end", help="sets loop end segment")
|
||||
parser.add_argument("-cv","--command-volume", help="sets volume")
|
||||
parser.add_argument("-c","--command", help="sets any command (free text)")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def is_file_ok(args, glob_file):
|
||||
if not os.path.isfile(glob_file):
|
||||
return False
|
||||
|
||||
if glob_file.endswith(".py"):
|
||||
return False
|
||||
|
||||
if args.filter:
|
||||
filename_test = os.path.basename(glob_file)
|
||||
p = re.compile(args.filter)
|
||||
if (p.match(filename_test) != None):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_txtp_name(args, segment):
|
||||
txtp_name = ''
|
||||
|
||||
if args.name:
|
||||
txtp_name = args.name
|
||||
|
||||
elif args.single:
|
||||
txtp_name = os.path.splitext(os.path.basename(segment))[0]
|
||||
|
||||
else:
|
||||
txtp_name = os.path.splitext(os.path.basename(args.files))[0]
|
||||
|
||||
txtp_name = txtp_name.replace('*', '')
|
||||
txtp_name = txtp_name.replace('?', '')
|
||||
|
||||
if txtp_name.endswith('_'):
|
||||
txtp_name = txtp_name[:-1]
|
||||
if txtp_name == '':
|
||||
txtp_name = 'bgm'
|
||||
|
||||
if not txtp_name.endswith(".txtp"):
|
||||
txtp_name += ".txtp"
|
||||
return txtp_name
|
||||
|
||||
def main():
|
||||
args = parse()
|
||||
|
||||
# get target files
|
||||
glob_files = glob.glob(args.files)
|
||||
|
||||
# process matches and add to output list
|
||||
files = []
|
||||
segments = []
|
||||
for glob_file in glob_files:
|
||||
if not is_file_ok(args, glob_file):
|
||||
continue
|
||||
|
||||
if args.single:
|
||||
name = get_txtp_name(args, glob_file)
|
||||
segments = [glob_file]
|
||||
files.append( (name,segments) )
|
||||
|
||||
else:
|
||||
segments.append(glob_file)
|
||||
|
||||
if not args.single:
|
||||
name = get_txtp_name(args, '')
|
||||
files.append( (name,segments) )
|
||||
|
||||
if not files or not segments:
|
||||
print("no files found")
|
||||
exit()
|
||||
|
||||
|
||||
# list info
|
||||
for name, segments in files:
|
||||
print("file: " + name)
|
||||
for segment in segments:
|
||||
print(" " + segment)
|
||||
|
||||
if args.list:
|
||||
exit()
|
||||
|
||||
# write resulting files
|
||||
for name, segments in files:
|
||||
with open(name,"w+") as ftxtp:
|
||||
for segment in segments:
|
||||
ftxtp.write(segment + "\n")
|
||||
if args.command_loop_start:
|
||||
ftxtp.write("loop_start_segment = " + args.command_loop_start + "\n")
|
||||
if args.command_loop_end:
|
||||
ftxtp.write("loop_end_segment = " + args.command_loop_end + "\n")
|
||||
if args.command_volume:
|
||||
ftxtp.write("commands = #@volume " + args.command_volume + "\n")
|
||||
if args.command:
|
||||
ftxtp.write(args.command + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -428,6 +428,7 @@ static const char* extension_list[] = {
|
||||
"sx",
|
||||
"sxd",
|
||||
"sxd2",
|
||||
"sxd3",
|
||||
|
||||
"tec",
|
||||
"tgq",
|
||||
|
@ -114,6 +114,7 @@ fail:
|
||||
/* SPW (SEWave) - from PlayOnline viewer for Final Fantasy XI (PC) */
|
||||
VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE *temp_streamFile = NULL;
|
||||
uint32_t codec, file_size, block_size, sample_rate, block_align;
|
||||
int32_t loop_start;
|
||||
off_t start_offset;
|
||||
@ -139,8 +140,8 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
|
||||
/*0x2c: unk (0x00?) */
|
||||
/*0x2d: unk (0x00/01?) */
|
||||
channel_count = read_8bit(0x2a,streamFile);
|
||||
block_align = read_8bit(0x2b,streamFile);
|
||||
/*0x2c: unk (0x01 when PCM, 0x10 when VAG?) */
|
||||
/*0x2b: unk (0x01 when PCM, 0x10 when VAG?) */
|
||||
block_align = read_8bit(0x2c,streamFile);
|
||||
|
||||
if (file_size != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
@ -181,6 +182,38 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
|
||||
|
||||
break;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
case 3: { /* ATRAC3 (encrypted) */
|
||||
uint8_t buf[0x100];
|
||||
int bytes, joint_stereo, skip_samples;
|
||||
size_t data_size = file_size - start_offset;
|
||||
|
||||
vgmstream->num_samples = block_size; /* atrac3_bytes_to_samples gives the same value */
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
block_align = 0xC0 * vgmstream->channels; /* 0x00 in header */
|
||||
joint_stereo = 0;
|
||||
skip_samples = 0;
|
||||
|
||||
bytes = ffmpeg_make_riff_atrac3(buf, 0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_align, joint_stereo, skip_samples);
|
||||
if (bytes <= 0) goto fail;
|
||||
|
||||
temp_streamFile = setup_bgw_atrac3_streamfile(streamFile, start_offset,data_size, 0xC0,channel_count);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0,data_size);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
@ -193,6 +226,7 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) {
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -5,32 +5,62 @@
|
||||
/* SXD - Sony/SCE's SNDX lib format (cousin of SGXD) [Gravity Rush, Freedom Wars, Soul Sacrifice PSV] */
|
||||
VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * streamHeader = NULL, *streamData = NULL;
|
||||
STREAMFILE *streamHeader = NULL, *streamExternal = NULL, *streamData = NULL, *streamHead = NULL, *streamBody = NULL;
|
||||
off_t start_offset, chunk_offset, first_offset = 0x60, name_offset = 0;
|
||||
size_t chunk_size, stream_size = 0;
|
||||
|
||||
int is_dual = 0, is_external = 0;
|
||||
int is_dual, is_external;
|
||||
int loop_flag, channels, codec, flags;
|
||||
int sample_rate, num_samples, loop_start_sample, loop_end_sample;
|
||||
uint32_t at9_config_data = 0;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* .sxd: header+data (SXDF), .sxd1: header (SXDF) + .sxd2 = data (SXDS) */
|
||||
if (!check_extensions(streamFile,"sxd,sxd2")) goto fail;
|
||||
is_dual = !check_extensions(streamFile,"sxd");
|
||||
|
||||
/* sxd1+sxd2: use sxd1 as header; otherwise use the current file as header */
|
||||
if (is_dual) {
|
||||
if (read_32bitBE(0x00,streamFile) != 0x53584453) /* "SXDS" */
|
||||
/* checks */
|
||||
/* .sxd: header+data (SXDF)
|
||||
* .sxd1: header (SXDF) + .sxd2 = data (SXDS)
|
||||
* .sxd3: sxd1 + sxd2 pasted together (found in some PS4 games, ex. Fate Extella)*/
|
||||
if (!check_extensions(streamFile,"sxd,sxd2,sxd3"))
|
||||
goto fail;
|
||||
streamHeader = open_streamfile_by_ext(streamFile, "sxd1");
|
||||
if (!streamHeader) goto fail;
|
||||
} else {
|
||||
streamHeader = streamFile;
|
||||
|
||||
/* setup head/body variations */
|
||||
if (check_extensions(streamFile,"sxd2")) {
|
||||
/* sxd1+sxd2: open sxd1 as header */
|
||||
|
||||
streamHead = open_streamfile_by_ext(streamFile, "sxd1");
|
||||
if (!streamHead) goto fail;
|
||||
|
||||
streamHeader = streamHead;
|
||||
streamExternal = streamFile;
|
||||
is_dual = 1;
|
||||
}
|
||||
if (read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */
|
||||
else if (check_extensions(streamFile,"sxd3")) {
|
||||
/* sxd3: make subfiles for head and body to simplify parsing */
|
||||
off_t sxd1_offset = 0x00;
|
||||
size_t sxd1_size = read_32bitLE(0x08, streamFile);
|
||||
off_t sxd2_offset = sxd1_size;
|
||||
size_t sxd2_size = get_streamfile_size(streamFile) - sxd1_size;
|
||||
|
||||
streamHead = setup_subfile_streamfile(streamFile, sxd1_offset, sxd1_size, "sxd1");
|
||||
if (!streamHead) goto fail;
|
||||
|
||||
streamBody = setup_subfile_streamfile(streamFile, sxd2_offset, sxd2_size, "sxd2");
|
||||
if (!streamBody) goto fail;
|
||||
|
||||
streamHeader = streamHead;
|
||||
streamExternal = streamBody;
|
||||
is_dual = 1;
|
||||
}
|
||||
else {
|
||||
/* sxd: use the current file as header */
|
||||
streamHeader = streamFile;
|
||||
streamExternal = NULL;
|
||||
is_dual = 0;
|
||||
}
|
||||
|
||||
if (streamHeader && read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */
|
||||
goto fail;
|
||||
if (streamExternal && read_32bitBE(0x00,streamExternal) != 0x53584453) /* "SXDS" */
|
||||
goto fail;
|
||||
|
||||
|
||||
@ -132,8 +162,9 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* even dual files may have some non-external streams */
|
||||
if (is_external) {
|
||||
streamData = streamFile;
|
||||
streamData = streamExternal;
|
||||
} else {
|
||||
streamData = streamHeader;
|
||||
}
|
||||
@ -196,11 +227,13 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) {
|
||||
if (!vgmstream_open_stream(vgmstream,streamData,start_offset))
|
||||
goto fail;
|
||||
|
||||
if (is_dual) close_streamfile(streamHeader);
|
||||
if (streamHead) close_streamfile(streamHead);
|
||||
if (streamBody) close_streamfile(streamBody);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
if (is_dual) close_streamfile(streamHeader);
|
||||
if (streamHead) close_streamfile(streamHead);
|
||||
if (streamBody) close_streamfile(streamBody);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1325,7 +1325,7 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
|
||||
int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile);
|
||||
|
||||
if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) {
|
||||
VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset);
|
||||
VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset);
|
||||
if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */
|
||||
goto fail;
|
||||
}
|
||||
@ -1451,6 +1451,14 @@ static int parse_stream_codec(ubi_sb_header * sb) {
|
||||
sb->stream_type = 0; /* probably ignored for non-stream types */
|
||||
}
|
||||
|
||||
/* Batman: Rise of Sin Tzu (Xbox)
|
||||
* (similar but also for some streams, maybe uses a 'hardware flag' possibly at 0x20?) */
|
||||
if ((sb->version == 0x000A0003 && sb->platform == UBI_XBOX) &&
|
||||
(!sb->is_external || strncmp(sb->resource_name, "STREAMHW.", 9) == 0)) {
|
||||
sb->stream_type = 0;
|
||||
}
|
||||
|
||||
|
||||
/* guess codec */
|
||||
switch (sb->stream_type) {
|
||||
case 0x00: /* platform default (rarely external) */
|
||||
@ -2068,7 +2076,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
|
||||
|
||||
sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34;
|
||||
|
||||
if (sb->version <= 0x00000200) {
|
||||
if (sb->version == 0x00000000 || sb->version == 0x00000200) {
|
||||
sb->cfg.audio_stream_size = 0x0c;
|
||||
sb->cfg.audio_stream_offset = 0x10;
|
||||
//sb->cfg.audio_extra_offset = 0x10;
|
||||
@ -2166,7 +2174,9 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
/* Batman: Vengeance (2001)(PC)-map */
|
||||
if (sb->version == 0x00000003 && sb->platform == UBI_PC) {
|
||||
/* Batman: Vengeance (2001)(Xbox)-map */
|
||||
if ((sb->version == 0x00000003 && sb->platform == UBI_PC) ||
|
||||
(sb->version == 0x00000003 && sb->platform == UBI_XBOX)) {
|
||||
config_sb_entry(sb, 0x40, 0x68);
|
||||
|
||||
config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */
|
||||
@ -2385,6 +2395,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */
|
||||
if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) {
|
||||
config_sb_entry(sb, 0x64, 0x80);
|
||||
|
||||
config_sb_audio_fs(sb, 0x24, 0x28, 0x2c);
|
||||
config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); /* stream_type may contain garbage */
|
||||
sb->cfg.audio_has_internal_names = 1;
|
||||
|
||||
config_sb_sequence(sb, 0x28, 0x14);
|
||||
|
||||
config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30);
|
||||
config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10);
|
||||
//todo some sequences mix 1ch and 2ch (voices?)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */
|
||||
if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) ||
|
||||
(sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) {
|
||||
|
Loading…
Reference in New Issue
Block a user