mirror of
https://codeberg.org/tenacityteam/tenacity
synced 2025-09-27 16:56:19 +02:00
mod-mka: Add back utilities
These utilities are required for the acutal exporter to work. Add them back first to get things started. Signed-off-by: Avery King <gperson@disroot.org>
This commit is contained in:
@@ -6,6 +6,7 @@ set(TARGET mod-mka)
|
||||
|
||||
set(SOURCES
|
||||
ImportMka.cpp
|
||||
ExportMka.cpp
|
||||
Mka.cpp
|
||||
)
|
||||
|
||||
|
289
modules/import-export/mod-mka/ExportMka.cpp
Normal file
289
modules/import-export/mod-mka/ExportMka.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/**********************************************************************
|
||||
|
||||
Tenacity: A Digital Audio Editor
|
||||
|
||||
ExportMka.cpp
|
||||
|
||||
Steve Lhomme
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Tags.h"
|
||||
#if defined(_CRTDBG_MAP_ALLOC) && LIBMATROSKA_VERSION < 0x010702
|
||||
// older libmatroska headers use std::nothrow which is incompatible with <crtdbg.h>
|
||||
#undef new
|
||||
#endif
|
||||
|
||||
#include <ebml/EbmlHead.h>
|
||||
#include <ebml/EbmlSubHead.h>
|
||||
#include <ebml/EbmlVoid.h>
|
||||
#include <ebml/StdIOCallback.h>
|
||||
#include <matroska/KaxSegment.h>
|
||||
#include <matroska/KaxSeekHead.h>
|
||||
#include <matroska/KaxCluster.h>
|
||||
#include <matroska/KaxVersion.h>
|
||||
|
||||
#if LIBMATROSKA_VERSION < 0x010700
|
||||
typedef enum {
|
||||
MATROSKA_TRACK_TYPE_VIDEO = 0x1, // An image.
|
||||
MATROSKA_TRACK_TYPE_AUDIO = 0x2, // Audio samples.
|
||||
MATROSKA_TRACK_TYPE_COMPLEX = 0x3, // A mix of different other TrackType. The codec needs to define how the `Matroska Player` should interpret such data.
|
||||
MATROSKA_TRACK_TYPE_LOGO = 0x10, // An image to be rendered over the video track(s).
|
||||
MATROSKA_TRACK_TYPE_SUBTITLE = 0x11, // Subtitle or closed caption data to be rendered over the video track(s).
|
||||
MATROSKA_TRACK_TYPE_BUTTONS = 0x12, // Interactive button(s) to be rendered over the video track(s).
|
||||
MATROSKA_TRACK_TYPE_CONTROL = 0x20, // Metadata used to control the player of the `Matroska Player`.
|
||||
MATROSKA_TRACK_TYPE_METADATA = 0x21, // Timed metadata that can be passed on to the `Matroska Player`.
|
||||
} MatroskaTrackType;
|
||||
|
||||
typedef enum {
|
||||
MATROSKA_TARGET_TYPE_COLLECTION = 70, // The highest hierarchical level that tags can describe.
|
||||
MATROSKA_TARGET_TYPE_EDITION = 60, // A list of lower levels grouped together.
|
||||
MATROSKA_TARGET_TYPE_ALBUM = 50, // The most common grouping level of music and video (equals to an episode for TV series).
|
||||
MATROSKA_TARGET_TYPE_PART = 40, // When an album or episode has different logical parts.
|
||||
MATROSKA_TARGET_TYPE_TRACK = 30, // The common parts of an album or movie.
|
||||
MATROSKA_TARGET_TYPE_SUBTRACK = 20, // Corresponds to parts of a track for audio (like a movement).
|
||||
MATROSKA_TARGET_TYPE_SHOT = 10, // The lowest hierarchy found in music or movies.
|
||||
} MatroskaTargetTypeValue;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBFLAC
|
||||
#include "FLAC++/encoder.h"
|
||||
#endif
|
||||
|
||||
using namespace libmatroska;
|
||||
|
||||
//// Utilities for the exporter ///////////////////////////////////////////////
|
||||
static uint64_t GetRandomUID64()
|
||||
{
|
||||
uint64_t uid = 0;
|
||||
for (size_t i=0; i<(64-7); i+=7)
|
||||
{
|
||||
auto r = static_cast<uint64_t>(std::rand() & 0x7fff);
|
||||
uid = uid << 7 | r;
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
static void FillRandomUUID(binary UID[16])
|
||||
{
|
||||
uint64_t rand;
|
||||
rand = GetRandomUID64();
|
||||
memcpy(&UID[0], &rand, 8);
|
||||
rand = GetRandomUID64();
|
||||
memcpy(&UID[8], &rand, 8);
|
||||
}
|
||||
|
||||
static void SetMetadata(const Tags *tags, KaxTag * & PrevTag, KaxTags & Tags, const wxChar *tagName, const MatroskaTargetTypeValue TypeValue, const wchar_t *mkaName)
|
||||
{
|
||||
if (tags != nullptr && tags->HasTag(tagName))
|
||||
{
|
||||
KaxTag &tag = AddNewChild<KaxTag>(Tags);
|
||||
KaxTagTargets &tagTarget = GetChild<KaxTagTargets>(tag);
|
||||
(EbmlUInteger &) GetChild<KaxTagTargetTypeValue>(tagTarget) = TypeValue;
|
||||
|
||||
KaxTagSimple &simpleTag = GetChild<KaxTagSimple>(tag);
|
||||
(EbmlUnicodeString &) GetChild<KaxTagName>(simpleTag) = (UTFstring)mkaName;
|
||||
(EbmlUnicodeString &) GetChild<KaxTagString>(simpleTag) = (UTFstring)tags->GetTag(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint64 MS_PER_FRAME = 40;
|
||||
|
||||
class ClusterMuxer
|
||||
{
|
||||
public:
|
||||
struct MuxerTime
|
||||
{
|
||||
uint64 prevEndTime{0};
|
||||
uint64 samplesRead{0};
|
||||
|
||||
/*const*/ uint64 TimeUnit;
|
||||
/*const*/ double rate;
|
||||
|
||||
// MuxerTime(uint64 _TimeUnit, double _rate)
|
||||
// :TimeUnit(_TimeUnit)
|
||||
// ,rate(_rate)
|
||||
// {
|
||||
// }
|
||||
|
||||
// MuxerTime(const MuxerTime & source) = default;
|
||||
|
||||
void AddSample(size_t samplesThisRun)
|
||||
{
|
||||
samplesRead += samplesThisRun;
|
||||
// in rounded TimeUnit as the drift with the actual time accumulates
|
||||
prevEndTime = std::llround(samplesRead * 1000000000. / (TimeUnit * rate));
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
ClusterMuxer(KaxSegment &_FileSegment, KaxTrackEntry &_Track, KaxCues &_AllCues, MuxerTime _muxerTime)
|
||||
:FileSegment(_FileSegment)
|
||||
,MyTrack1(_Track)
|
||||
,AllCues(_AllCues)
|
||||
,Time(_muxerTime)
|
||||
,maxFrameSamples(MS_PER_FRAME * _muxerTime.rate / 1000) // match mkvmerge
|
||||
{
|
||||
|
||||
Cluster = std::make_unique<KaxCluster>();
|
||||
Cluster->SetParent(FileSegment); // mandatory to store references in this Cluster
|
||||
Cluster->InitTimecode(Time.prevEndTime, Time.TimeUnit);
|
||||
Cluster->EnableChecksum();
|
||||
|
||||
framesBlob = std::make_unique<KaxBlockBlob>(BLOCK_BLOB_SIMPLE_AUTO);
|
||||
framesBlob->SetParent(*Cluster);
|
||||
AllCues.AddBlockBlob(*framesBlob);
|
||||
}
|
||||
|
||||
// return true when the Muxer is full
|
||||
bool AddBuffer(DataBuffer &dataBuff, size_t samplesThisRun)
|
||||
{
|
||||
if (!framesBlob)
|
||||
{
|
||||
framesBlob = std::make_unique<KaxBlockBlob>(BLOCK_BLOB_SIMPLE_AUTO);
|
||||
framesBlob->SetParent(*Cluster);
|
||||
}
|
||||
// TODO check the timestamp is OK within the Cluster first
|
||||
if (!framesBlob->AddFrameAuto(MyTrack1, Time.prevEndTime * Time.TimeUnit, dataBuff, LACING_NONE))
|
||||
{
|
||||
// last frame allowed in the lace, we need a new frame blob
|
||||
FinishFrameBlock(maxFrameSamples - samplesThisRun);
|
||||
}
|
||||
|
||||
Time.AddSample(samplesThisRun);
|
||||
|
||||
sampleWritten += samplesThisRun;
|
||||
|
||||
if (sampleWritten >= (uint64)(10 * maxFrameSamples))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
MuxerTime Finish(IOCallback & mka_file, KaxSeekHead & MetaSeek)
|
||||
{
|
||||
if (sampleWritten)
|
||||
{
|
||||
FinishFrameBlock(maxFrameSamples);
|
||||
Cluster->Render(mka_file, AllCues);
|
||||
// No need to use SeekHead for Cluster, there's Cues for that
|
||||
// MetaSeek.IndexThis(*Cluster, FileSegment);
|
||||
}
|
||||
return Time;
|
||||
}
|
||||
|
||||
private:
|
||||
void FinishFrameBlock(const size_t lessSamples)
|
||||
{
|
||||
if (framesBlob)
|
||||
{
|
||||
if (lessSamples != 0)
|
||||
{
|
||||
// last block, write the duration
|
||||
// TODO get frames from the blob and add them in a BLOCK_BLOB_NO_SIMPLE one
|
||||
// framesBlob->SetBlockDuration(lessSamples * Time.TimestampScale / Time.rate);
|
||||
}
|
||||
Cluster->AddBlockBlob(framesBlob.release());
|
||||
}
|
||||
}
|
||||
|
||||
KaxSegment &FileSegment;
|
||||
KaxTrackEntry &MyTrack1;
|
||||
KaxCues &AllCues;
|
||||
const size_t maxFrameSamples;
|
||||
|
||||
MuxerTime Time;
|
||||
|
||||
std::unique_ptr<KaxCluster> Cluster;
|
||||
std::unique_ptr<KaxBlockBlob> framesBlob;
|
||||
uint64_t sampleWritten{0};
|
||||
};
|
||||
|
||||
#ifdef USE_LIBFLAC
|
||||
class MkaFLACEncoder : public FLAC::Encoder::Stream
|
||||
{
|
||||
public:
|
||||
::FLAC__StreamEncoderInitStatus init() override
|
||||
{
|
||||
auto ret = FLAC::Encoder::Stream::init();
|
||||
initializing = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<FLAC__byte> & GetInitBuffer()
|
||||
{
|
||||
return initBuffer;
|
||||
}
|
||||
|
||||
bool Process(std::unique_ptr<ClusterMuxer> & Muxer, const FLAC__int32 * const buffer[], uint32_t samples)
|
||||
{
|
||||
muxer = &Muxer;
|
||||
muxer_full = false;
|
||||
bool ret = process(buffer, samples);
|
||||
if (!ret)
|
||||
return true; // on error assume the Cluster is full
|
||||
return muxer_full;
|
||||
}
|
||||
|
||||
bool finish() override
|
||||
{
|
||||
finishing = true;
|
||||
return FLAC::Encoder::Stream::finish();
|
||||
}
|
||||
|
||||
bool hasNewCodecPrivate() const
|
||||
{
|
||||
return initializing || overwriting;
|
||||
}
|
||||
|
||||
protected:
|
||||
::FLAC__StreamEncoderWriteStatus write_callback(const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t /*current_frame*/) override
|
||||
{
|
||||
if (initializing)
|
||||
{
|
||||
std::size_t size = initBuffer.size();
|
||||
initBuffer.resize(size + bytes);
|
||||
memcpy(&initBuffer[size], buffer, bytes);
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
if (overwriting)
|
||||
{
|
||||
if (initOffset + bytes > initBuffer.size())
|
||||
{
|
||||
// we can't expand the CodePrivate
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
|
||||
}
|
||||
memcpy(&initBuffer[initOffset], buffer, bytes);
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
|
||||
DataBuffer *dataBuff = new DataBuffer((binary*)buffer, bytes, nullptr, true);
|
||||
if ((*muxer)->AddBuffer(*dataBuff, samples))
|
||||
muxer_full = true;
|
||||
|
||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
||||
}
|
||||
|
||||
::FLAC__StreamEncoderSeekStatus seek_callback(FLAC__uint64 absolute_byte_offset) override
|
||||
{
|
||||
// support overwriting the CodecPrivate with the checksum
|
||||
if (finishing && absolute_byte_offset < (initBuffer.size() + initOffset))
|
||||
{
|
||||
overwriting = true;
|
||||
initOffset = absolute_byte_offset;
|
||||
return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
|
||||
}
|
||||
return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
|
||||
}
|
||||
|
||||
bool initializing{true};
|
||||
bool finishing{false};
|
||||
bool overwriting{false};
|
||||
std::vector<FLAC__byte> initBuffer;
|
||||
FLAC__uint64 initOffset{0};
|
||||
|
||||
std::unique_ptr<ClusterMuxer> *muxer = nullptr;
|
||||
bool muxer_full;
|
||||
};
|
||||
#endif
|
||||
///////////////////////////////////////////////////////////////////////////////
|
Reference in New Issue
Block a user