summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniil Cherednik <[email protected]>2026-05-26 21:32:54 +0200
committerDaniil Cherednik <[email protected]>2026-05-26 21:32:54 +0200
commit01234b09a2fb3e7fc6746ace7554383b0caa97e1 (patch)
treec7020a438cba8d29eb677a7036476fd9f679744e
parent5a97fec0876fb6052996311498ea5bd707049c69 (diff)
Add explicit container selectionHEADmaster
-rw-r--r--CMakeLists.txt1
-rw-r--r--README.md5
-rw-r--r--man/atracdenc.116
-rw-r--r--src/help.cpp6
-rw-r--r--src/main.cpp221
-rw-r--r--test/integration/input_file_tests.py46
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)
diff --git a/README.md b/README.md
index 5228420..e66141a 100644
--- a/README.md
+++ b/README.md
@@ -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__":