diff options
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | man/atracdenc.1 | 16 | ||||
| -rw-r--r-- | src/help.cpp | 6 | ||||
| -rw-r--r-- | src/main.cpp | 221 | ||||
| -rw-r--r-- | test/integration/input_file_tests.py | 46 |
6 files changed, 258 insertions, 37 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e849f5..813c74d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ if (ATRACDENC_PYTHON_EXECUTABLE) add_input_file_test(output_utf8_aea_filename utf8-output-aea) add_input_file_test(decode_utf8_input_filename utf8-decode-input) add_input_file_test(decode_utf8_output_filename utf8-decode-output) + add_input_file_test(explicit_container explicit-container) endif() if (GTest_FOUND) @@ -81,6 +81,11 @@ The binary is created at `build-msvc/src/Release/atracdenc.exe`. Usage: +By default the output container is selected from the output file extension. +Use `--container {aea|oma|riff|rm|raw}` to select it explicitly. +Valid combinations are ATRAC1: AEA/RAW, ATRAC3: OMA/RIFF/RM/RAW, +and ATRAC3PLUS: OMA/RIFF/RAW. + ATRAC1: ``` ./atracdenc -e atrac1 -i ~/01.wav -o /tmp/01.aea diff --git a/man/atracdenc.1 b/man/atracdenc.1 index f6a91a2..8727798 100644 --- a/man/atracdenc.1 +++ b/man/atracdenc.1 @@ -27,14 +27,14 @@ atracdenc [options] {-e <codec> | \[en]encode=<codec> | -d | atracdenc is an audio converter that can encode into ATRAC1, ATRAC3 or ATRAC3PLUS compatible format and decode from ATRAC1. .PP -For ATRAC1 (also known as ATRAC SP on MiniDisk devices) AEA audio -container is used. Only one bitrate (292 kbit/s) is specified. +For ATRAC1 (also known as ATRAC SP on MiniDisk devices) AEA and RAW +containers are supported. Only one bitrate (292 kbit/s) is specified. .PP -For ATRCA3 (also known as ATRAC LP on MiniDisk devices) OMA, AT3(riff) -and RealMedia container is supported. In the case of OMA and AT3 container, compatible bitrates are 66150 (LP4), 104738 and 132300 (LP2) bits/s. +For ATRAC3 (also known as ATRAC LP on MiniDisk devices) OMA, AT3(RIFF), +RealMedia and RAW containers are supported. In the case of OMA and AT3 container, compatible bitrates are 66150 (LP4), 104738 and 132300 (LP2) bits/s. The RealMedia container supports 66150, 93713, 104738, 132300, 146081, 176400, 264600 and 352800 bits/s. .PP -For ATRAC3PLUS currently only OMA container is supported with highest bitrate 352800 bits/s +For ATRAC3PLUS OMA, AT3(RIFF) and RAW containers are supported with highest bitrate 352800 bits/s .SH OPTIONS .TP .B \-h @@ -45,7 +45,11 @@ Path to the input file. In case of encoding it should be WAV, AIFF or SND 44100 Use - to read from stdin. .TP .B \-o -Path to the output file. The container format is chosen automaticaly according to the file extension. +Path to the output file. The container format is chosen automatically according to the file extension unless --container is specified. +.TP +.B \--container <container> +Explicitly select the output container. <container> must be one of aea, oma, riff, rm or raw. +Valid combinations are ATRAC1: aea/raw; ATRAC3: oma/riff/rm/raw; ATRAC3PLUS: oma/riff/raw. .TP .B \-e <codec> Encode mode. <codec> is a codec name must be one of atrac1, atrac3, atrac3_lp4 or atarc3plus diff --git a/src/help.cpp b/src/help.cpp index d403765..511a272 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -15,6 +15,12 @@ atracdenc {-e <codec> | --encode=<codec> | -d | --decode} -i <in> -o <out> -o path to output file -h print help and exit +--container explicitly select output container: aea, oma, riff, rm, raw + valid combinations: + ATRAC1: aea, raw + ATRAC3: oma, riff, rm, raw + ATRAC3PLUS: oma, riff, raw + --bitrate allow to specify bitrate (for ATRAC3 + RealMedia container only) Advanced options: diff --git a/src/main.cpp b/src/main.cpp index 2c2de1e..854105d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <algorithm> +#include <cctype> +#include <cstring> #include <iostream> #include <fstream> #include <string> @@ -79,6 +82,158 @@ static string GetFileExt(const string& path) { return ext; } +static string ToLowerAscii(string value) +{ + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); }); + return value; +} + +enum class EContainer { + AUTO, + AEA, + OMA, + RIFF, + RM, + RAW, +}; + +enum class ECodec { + ATRAC1, + ATRAC3, + ATRAC3PLUS, +}; + +static EContainer ParseContainer(const char* value) +{ + const string container = ToLowerAscii(value ? string(value) : string()); + if (container == "aea") + return EContainer::AEA; + if (container == "oma") + return EContainer::OMA; + if (container == "riff") + return EContainer::RIFF; + if (container == "rm") + return EContainer::RM; + if (container == "raw") + return EContainer::RAW; + throw std::invalid_argument("unrecognized container: " + container); +} + +static const char* ContainerName(EContainer container) +{ + switch (container) { + case EContainer::AEA: + return "AEA"; + case EContainer::OMA: + return "OMA"; + case EContainer::RIFF: + return "RIFF"; + case EContainer::RM: + return "RM"; + case EContainer::RAW: + return "RAW"; + case EContainer::AUTO: + return "AUTO"; + } + return "UNKNOWN"; +} + +static const char* CodecName(ECodec codec) +{ + switch (codec) { + case ECodec::ATRAC1: + return "ATRAC1"; + case ECodec::ATRAC3: + return "ATRAC3"; + case ECodec::ATRAC3PLUS: + return "ATRAC3PLUS"; + } + return "UNKNOWN"; +} + +static const char* ValidContainerNames(ECodec codec) +{ + switch (codec) { + case ECodec::ATRAC1: + return "AEA, RAW"; + case ECodec::ATRAC3: + return "OMA, RIFF, RM, RAW"; + case ECodec::ATRAC3PLUS: + return "OMA, RIFF, RAW"; + } + return ""; +} + +static bool IsValidContainer(ECodec codec, EContainer container) +{ + switch (codec) { + case ECodec::ATRAC1: + return container == EContainer::AEA || container == EContainer::RAW; + case ECodec::ATRAC3: + return container == EContainer::OMA || container == EContainer::RIFF || + container == EContainer::RM || container == EContainer::RAW; + case ECodec::ATRAC3PLUS: + return container == EContainer::OMA || container == EContainer::RIFF || + container == EContainer::RAW; + } + return false; +} + +static void CheckContainer(ECodec codec, EContainer container) +{ + if (!IsValidContainer(codec, container)) { + string err = "Container "; + err += ContainerName(container); + err += " is not supported for "; + err += CodecName(codec); + err += "; valid containers are: "; + err += ValidContainerNames(codec); + throw std::runtime_error(err); + } +} + +static EContainer SelectAtrac1Container(const string& outFile, EContainer requestedContainer) +{ + if (requestedContainer != EContainer::AUTO) + return requestedContainer; + + const string ext = ToLowerAscii(GetFileExt(outFile)); + if (ext == "raw" || ext == "dat") + return EContainer::RAW; + return EContainer::AEA; +} + +static EContainer SelectAtrac3Container(const string& outFile, EContainer requestedContainer) +{ + if (requestedContainer != EContainer::AUTO) + return requestedContainer; + + const string ext = ToLowerAscii(GetFileExt(outFile)); + if (ext == "wav" || ext == "at3") + return EContainer::RIFF; + if (ext == "raw" || ext == "dat") + return EContainer::RAW; + if (ext == "rm") + return EContainer::RM; + return EContainer::OMA; +} + +static EContainer SelectAtrac3PlusContainer(const string& outFile, EContainer requestedContainer) +{ + if (requestedContainer != EContainer::AUTO) + return requestedContainer; + + const string ext = ToLowerAscii(GetFileExt(outFile)); + if (ext == "wav" || ext == "at3") + return EContainer::RIFF; + if (ext == "raw" || ext == "dat") + return EContainer::RAW; + if (ext == "rm") + return EContainer::RM; + return EContainer::OMA; +} + static int checkedStoi(const char* data, int min, int max, int def) { int tmp = 0; @@ -115,6 +270,7 @@ enum EOptions O_NOGAINCONTROL = 6, O_ADVANCED_OPT = 7, O_YAML_LOG = 8, + O_CONTAINER = 9, }; static void CheckInputFormat(const TWav* p) @@ -134,8 +290,9 @@ static TWavPtr OpenWavFile(const string& inFile) } static void PrepareAtrac1Encoder(const string& inFile, - const string& outFile, - const bool noStdOut, + const string& outFile, + const bool noStdOut, + EContainer requestedContainer, NAtrac1::TAtrac1EncodeSettings&& encoderSettings, uint64_t* totalSamples, TWavPtr* wavIO, @@ -157,15 +314,14 @@ static void PrepareAtrac1Encoder(const string& inFile, std::cerr << "Number of input samples exceeds output format limitation," "the result will be incorrect" << std::endl; } - const string ext = GetFileExt(outFile); + const EContainer container = SelectAtrac1Container(outFile, requestedContainer); + CheckContainer(ECodec::ATRAC1, container); TCompressedOutputPtr aeaIO; - string contName; - if (ext == "raw" || ext == "dat") { - contName = "raw ATRAC1"; + const string contName = ContainerName(container); + if (container == EContainer::RAW) { aeaIO = CreateRawOutput(outFile, numChannels, TAtrac1Data::SoundUnitSize); } else { - contName = "AEA"; aeaIO = CreateAeaOutput(outFile, "test", numChannels, (uint32_t)numFrames); } @@ -211,6 +367,7 @@ static void PrepareAtrac1Decoder(const string& inFile, static void PrepareAtrac3Encoder(const string& inFile, const string& outFile, const bool noStdOut, + EContainer requestedContainer, NAtrac3::TAtrac3EncoderSettings&& encoderSettings, uint64_t* totalSamples, const TWavPtr& wavIO, @@ -225,26 +382,23 @@ static void PrepareAtrac3Encoder(const string& inFile, "the result will be incorrect" << std::endl; } - const string ext = GetFileExt(outFile); + const EContainer container = SelectAtrac3Container(outFile, requestedContainer); + CheckContainer(ECodec::ATRAC3, container); TCompressedOutputPtr omaIO; - string contName; - if (ext == "wav" || ext == "at3") { - contName = "AT3 (RIFF)"; + const string contName = ContainerName(container); + if (container == EContainer::RIFF) { omaIO = CreateAt3Output(outFile, 2, numFrames, encoderSettings.ConteinerParams->FrameSz, encoderSettings.ConteinerParams->Js); - } else if (ext == "raw" || ext == "dat") { - contName = "raw ATRAC3"; + } else if (container == EContainer::RAW) { omaIO = CreateRawOutput(outFile, numChannels); - } else if (ext == "rm") { - contName = "RealMedia"; + } else if (container == EContainer::RM) { omaIO = CreateRmOutput(outFile, "test", numChannels, numFrames, encoderSettings.ConteinerParams->FrameSz, encoderSettings.ConteinerParams->Js); } else { - contName = "OMA"; omaIO.reset(new TOma(outFile, "test", numChannels, @@ -273,6 +427,7 @@ static void PrepareAtrac3Encoder(const string& inFile, static void PrepareAtrac3PEncoder(const string& inFile, const string& outFile, const bool noStdOut, + EContainer requestedContainer, int numChannels, uint64_t* totalSamples, const TWavPtr& wavIO, @@ -287,21 +442,17 @@ static void PrepareAtrac3PEncoder(const string& inFile, "the result will be incorrect" << std::endl; } - const string ext = GetFileExt(outFile); + const EContainer container = SelectAtrac3PlusContainer(outFile, requestedContainer); + CheckContainer(ECodec::ATRAC3PLUS, container); TCompressedOutputPtr omaIO; - string contName; - if (ext == "wav" || ext == "at3") { - contName = "AT3 (RIFF)"; + const string contName = ContainerName(container); + if (container == EContainer::RIFF) { omaIO = CreateAt3POutput(outFile, numChannels, numFrames, 2048); - } else if (ext == "raw" || ext == "dat") { - contName = "raw ATRAC3plus"; + } else if (container == EContainer::RAW) { omaIO = CreateRawOutput(outFile, numChannels); - } else if (ext == "rm") { - throw std::runtime_error("RealMedia container is not supported for ATRAC3PLUS"); } else { - contName = "OMA"; omaIO.reset(new TOma(outFile, "test", numChannels, @@ -349,6 +500,7 @@ int main_(int argc, char* const* argv) { "nogaincontrol", no_argument, NULL, O_NOGAINCONTROL}, { "advanced", required_argument, NULL, O_ADVANCED_OPT}, { "yaml-log", required_argument, NULL, O_YAML_LOG}, + { "container", required_argument, NULL, O_CONTAINER}, { NULL, 0, NULL, 0} }; @@ -361,6 +513,7 @@ int main_(int argc, char* const* argv) bool noGainControl = false; bool noTonalComponents = false; string yamlLogFile; + EContainer requestedContainer = EContainer::AUTO; NAtrac1::TAtrac1EncodeSettings::EWindowMode windowMode = NAtrac1::TAtrac1EncodeSettings::EWindowMode::EWM_AUTO; uint32_t winMask = 0; //0 - all is long uint32_t bitrate = 0; //0 - use default for codec @@ -437,6 +590,14 @@ int main_(int argc, char* const* argv) case O_YAML_LOG: yamlLogFile = optarg; break; + case O_CONTAINER: + try { + requestedContainer = ParseContainer(optarg); + } catch (const std::invalid_argument& ex) { + printUsage(myName, ex.what()); + return 1; + } + break; default: printUsage(myName); return 1; @@ -459,6 +620,10 @@ int main_(int argc, char* const* argv) cerr << "No out file" << endl; return 1; } + if (mode == E_DECODE && requestedContainer != EContainer::AUTO) { + cerr << "--container can only be used when encoding" << endl; + return 1; + } TPcmEnginePtr pcmEngine; TAtracProcessorPtr atracProcessor; @@ -476,7 +641,7 @@ int main_(int argc, char* const* argv) } using NAtrac1::TAtrac1Data; NAtrac1::TAtrac1EncodeSettings encoderSettings(bfuIdxConst, windowMode, winMask); - PrepareAtrac1Encoder(inFile, outFile, noStdOut, std::move(encoderSettings), + PrepareAtrac1Encoder(inFile, outFile, noStdOut, requestedContainer, std::move(encoderSettings), &totalSamples, &wavIO, &pcmEngine, &atracProcessor); pcmFrameSz = TAtrac1Data::NumSamples; } @@ -506,7 +671,7 @@ int main_(int argc, char* const* argv) NAtrac3::TAtrac3EncoderSettings encoderSettings(bitrate * 1024, noGainControl, noTonalComponents, wavIO->GetChannelNum(), bfuIdxConst, yamlOut); - PrepareAtrac3Encoder(inFile, outFile, noStdOut, std::move(encoderSettings), + PrepareAtrac3Encoder(inFile, outFile, noStdOut, requestedContainer, std::move(encoderSettings), &totalSamples, wavIO, &pcmEngine, &atracProcessor); pcmFrameSz = TAtrac3Data::NumSamples;; } @@ -514,7 +679,7 @@ int main_(int argc, char* const* argv) case (E_ENCODE | E_ATRAC3PLUS): { wavIO = OpenWavFile(inFile); - PrepareAtrac3PEncoder(inFile, outFile, noStdOut, wavIO->GetChannelNum(), + PrepareAtrac3PEncoder(inFile, outFile, noStdOut, requestedContainer, wavIO->GetChannelNum(), &totalSamples, wavIO, &pcmEngine, &atracProcessor, advancedOpt); pcmFrameSz = 2048; } diff --git a/test/integration/input_file_tests.py b/test/integration/input_file_tests.py index 9686fb2..0bdee15 100644 --- a/test/integration/input_file_tests.py +++ b/test/integration/input_file_tests.py @@ -42,14 +42,17 @@ def require_output_file(path, proc, message): fail(message, proc) -def encode(exe, in_file, out_file, codec): - return run_command([ +def encode(exe, in_file, out_file, codec, container=None): + args = [ str(exe), "-e", codec, "--nostdout", "-i", str(in_file), "-o", str(out_file), - ]) + ] + if container is not None: + args.extend(["--container", container]) + return run_command(args) def decode_atrac1(exe, in_file, out_file): @@ -165,6 +168,40 @@ def check_utf8_decode_output(exe, work_dir): require_output_file(out_file, proc, "ATRAC1 decoding with a UTF-8 output filename did not create output") +def check_explicit_container(exe, work_dir): + in_file = work_dir / "explicit-container-input.wav" + write_wav(in_file, samples=8192) + + riff_out = work_dir / "explicit-riff-output.oma" + proc = encode(exe, in_file, riff_out, "atrac3", container="riff") + if proc.returncode != 0: + fail("ATRAC3 encoding failed with explicit RIFF container", proc) + require_output_file(riff_out, proc, "explicit RIFF container did not create output") + with riff_out.open("rb") as stream: + if stream.read(4) != b"RIFF": + fail("explicit RIFF container did not override the .oma extension", proc) + + raw_out = work_dir / "explicit-raw-output.aea" + proc = encode(exe, in_file, raw_out, "atrac1", container="raw") + if proc.returncode != 0: + fail("ATRAC1 encoding failed with explicit RAW container", proc) + require_output_file(raw_out, proc, "explicit RAW container did not create output") + + invalid_atrac1 = work_dir / "invalid-atrac1.oma" + proc = encode(exe, in_file, invalid_atrac1, "atrac1", container="oma") + if proc.returncode == 0: + fail("ATRAC1 encoding unexpectedly accepted OMA container", proc) + if "container oma is not supported for atrac1" not in (decode(proc.stdout) + decode(proc.stderr)).lower(): + fail("ATRAC1 invalid container error did not explain the rejected combination", proc) + + invalid_atrac3plus = work_dir / "invalid-atrac3plus.rm" + proc = encode(exe, in_file, invalid_atrac3plus, "atrac3plus", container="rm") + if proc.returncode == 0: + fail("ATRAC3PLUS encoding unexpectedly accepted RM container", proc) + if "container rm is not supported for atrac3plus" not in (decode(proc.stdout) + decode(proc.stderr)).lower(): + fail("ATRAC3PLUS invalid container error did not explain the rejected combination", proc) + + def main(): parser = argparse.ArgumentParser() parser.add_argument("--exe", required=True) @@ -178,6 +215,7 @@ def main(): "utf8-output-rm", "utf8-output-aea", "utf8-decode-input", + "explicit-container", "utf8-decode-output", ]) args = parser.parse_args() @@ -204,6 +242,8 @@ def main(): check_utf8_decode_input(exe, work_dir) elif args.case == "utf8-decode-output": check_utf8_decode_output(exe, work_dir) + elif args.case == "explicit-container": + check_explicit_container(exe, work_dir) if __name__ == "__main__": |
