Support Unicode paths when opening Ogg Vorbis files

This commit is contained in:
Daniel Wolf 2018-08-31 22:04:51 +02:00
parent 5e7e6f5f87
commit c22550f7f8
1 changed files with 47 additions and 10 deletions

View File

@ -1,16 +1,16 @@
#include "OggVorbisFileReader.h" #include "OggVorbisFileReader.h"
#include <stdlib.h>
#include "vorbis/codec.h" #include "vorbis/codec.h"
#include "vorbis/vorbisfile.h" #include "vorbis/vorbisfile.h"
#include "tools/tools.h" #include "tools/tools.h"
#include <format.h> #include <format.h>
#include <numeric>
#include "tools/fileTools.h" #include "tools/fileTools.h"
using boost::filesystem::path; using boost::filesystem::path;
using std::vector; using std::vector;
using std::make_shared; using std::make_shared;
using std::ifstream;
using std::ios_base;
std::string vorbisErrorToString(int64_t errorCode) { std::string vorbisErrorToString(int64_t errorCode) {
switch (errorCode) { switch (errorCode) {
@ -53,34 +53,71 @@ T throwOnError(T code) {
return code; return code;
} }
size_t readCallback(void* buffer, size_t elementSize, size_t elementCount, void* dataSource) {
assert(elementSize == 1);
ifstream& stream = *static_cast<ifstream*>(dataSource);
stream.read(static_cast<char*>(buffer), elementCount);
const std::streamsize bytesRead = stream.gcount();
stream.clear(); // In case we read past EOF
return static_cast<size_t>(bytesRead);
}
int seekCallback(void* dataSource, ogg_int64_t offset, int origin) {
static const vector<ios_base::seekdir> seekDirections{
ios_base::beg, ios_base::cur, ios_base::end
};
ifstream& stream = *static_cast<ifstream*>(dataSource);
stream.seekg(offset, seekDirections.at(origin));
stream.clear(); // In case we seeked to EOF
return 0;
}
long tellCallback(void* dataSource) {
ifstream& stream = *static_cast<ifstream*>(dataSource);
const auto position = stream.tellg();
assert(position >= 0);
return static_cast<long>(position);
}
// RAII wrapper around OggVorbis_File // RAII wrapper around OggVorbis_File
class OggVorbisFile { class OggVorbisFile {
public: public:
OggVorbisFile(const OggVorbisFile&) = delete; OggVorbisFile(const OggVorbisFile&) = delete;
OggVorbisFile& operator=(const OggVorbisFile&) = delete; OggVorbisFile& operator=(const OggVorbisFile&) = delete;
OggVorbisFile(const path& filePath) { OggVorbisFile(const path& filePath) :
throwOnError(ov_fopen(filePath.string().c_str(), &file)); stream(openFile(filePath))
{
// Throw only on badbit, not on failbit.
// Ogg Vorbis expects read operations past the end of the file to
// succeed, not to throw.
stream.exceptions(ifstream::badbit);
// Ogg Vorbis normally uses the `FILE` API from the C standard library.
// This doesn't handle Unicode paths on Windows.
// Use wrapper functions around `ifstream` instead.
const ov_callbacks callbacks{readCallback, seekCallback, nullptr, tellCallback};
throwOnError(ov_open_callbacks(&stream, &oggVorbisHandle, nullptr, 0, callbacks));
} }
OggVorbis_File* get() { OggVorbis_File* get() {
return &file; return &oggVorbisHandle;
} }
~OggVorbisFile() { ~OggVorbisFile() {
ov_clear(&file); ov_clear(&oggVorbisHandle);
} }
private: private:
OggVorbis_File file; OggVorbis_File oggVorbisHandle;
ifstream stream;
}; };
OggVorbisFileReader::OggVorbisFileReader(const path& filePath) : OggVorbisFileReader::OggVorbisFileReader(const path& filePath) :
filePath(filePath) filePath(filePath)
{ {
// Make sure that common error cases result in readable exception messages
throwIfNotReadable(filePath);
OggVorbisFile file(filePath); OggVorbisFile file(filePath);
vorbis_info* vorbisInfo = ov_info(file.get(), -1); vorbis_info* vorbisInfo = ov_info(file.get(), -1);