Skip to content

Commit

Permalink
feat: store tracklist as tag comment on recording
Browse files Browse the repository at this point in the history
  • Loading branch information
acolombier committed Oct 13, 2024
1 parent 34dde6f commit acc29fb
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 161 deletions.
26 changes: 26 additions & 0 deletions src/encoder/encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,29 @@ EncoderRecordingSettingsPointer EncoderFactory::getEncoderRecordingSettings(Enco
return std::make_shared<EncoderWaveSettings>(pConfig, ENCODING_WAVE);
}
}

void Encoder::addToTracklist(const QString& artist,
const QString& title,
std::chrono::seconds timecode) {
auto recordedDuration = timecode.count();
m_trackList.append(
QStringLiteral("%1: %2 - %3")
.arg(QString("%1:%2:%3")
.arg(recordedDuration / (60 * 60),
2,
'f',
0,
'0') // hours
.arg((recordedDuration / 60) % 60,
2,
'f',
0,
'0') // minutes
.arg(recordedDuration % 60, 2, 'f', 0, '0'),
artist.trimmed().isEmpty()
? QObject::tr("(Unknown Artist)")
: artist,
title.trimmed().isEmpty()
? QObject::tr("(Unknown Title)")
: title));
}
15 changes: 14 additions & 1 deletion src/encoder/encoder.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <chrono>
#include <memory>

#include "encoder/encoderrecordingsettings.h"
Expand Down Expand Up @@ -32,11 +33,23 @@ class Encoder {
virtual void encodeBuffer(const CSAMPLE *samples, const int size) = 0;
// Adds metadata to the encoded audio, i.e., the ID3 tag. Currently only used
// by EngineRecord, ShoutConnection does something different.
virtual void updateMetaData(const QString& artist, const QString& title, const QString& album) = 0;
virtual void updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode = {}) = 0;
// called at the end when encoding is finished
virtual void flush() = 0;
// Setup the encoder with the specific settings
virtual void setEncoderSettings(const EncoderSettings& settings) = 0;

protected:
void addToTracklist(const QString& artist, const QString& title, std::chrono::seconds timecode);
QStringList getTrackList() const {
return m_trackList;
}

private:
QStringList m_trackList;
};

typedef std::shared_ptr<Encoder> EncoderPointer;
Expand Down
13 changes: 10 additions & 3 deletions src/encoder/encoderfdkaac.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "encoder/encoderfdkaac.h"

#include <qglobal.h>

#ifdef __APPLE__
#include <QCoreApplication>
#endif
Expand Down Expand Up @@ -439,9 +441,14 @@ void EncoderFdkAac::processFIFO() {
}
}

void EncoderFdkAac::updateMetaData(
const QString& artist, const QString& title, const QString& album) {
(void)artist, (void)title, (void)album;
void EncoderFdkAac::updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode) {
Q_UNUSED(artist);
Q_UNUSED(title);
Q_UNUSED(album);
Q_UNUSED(timecode);
}

void EncoderFdkAac::flush() {
Expand Down
5 changes: 4 additions & 1 deletion src/encoder/encoderfdkaac.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ class EncoderFdkAac : public Encoder {

int initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) override;
void encodeBuffer(const CSAMPLE* samples, const int sampleCount) override;
void updateMetaData(const QString& artist, const QString& title, const QString& album) override;
void updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode = {}) override;
void flush() override;
void setEncoderSettings(const EncoderSettings& settings) override;

Expand Down
11 changes: 7 additions & 4 deletions src/encoder/encoderffmpegcore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,15 @@ void EncoderFfmpegCore::encodeBuffer(const CSAMPLE *samples, const int size) {
//
// Currently this method is used before init() once to save artist, title and album
//
void EncoderFfmpegCore::updateMetaData(const QString& artist, const QString& title, const QString& album) {
void EncoderFfmpegCore::updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode) {
qDebug() << "ffmpegencodercore: UpdateMetadata: !" << artist << " - " << title <<
" - " << album;
m_strMetaDataTitle = title;
m_strMetaDataArtist = artist;
m_strMetaDataAlbum = album;
Q_UNUSED(artist);
Q_UNUSED(title);
Q_UNUSED(album);
}

int EncoderFfmpegCore::initEncoder(
Expand Down
8 changes: 4 additions & 4 deletions src/encoder/encoderffmpegcore.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ class EncoderFfmpegCore : public Encoder {
~EncoderFfmpegCore();
int initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) override;
void encodeBuffer(const CSAMPLE *samples, const int size) override;
void updateMetaData(const QString& artist, const QString& title, const QString& album) override;
void updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode = {}) override;
void flush() override;
void setEncoderSettings(const EncoderSettings& settings) override;
protected:
Expand All @@ -70,9 +73,6 @@ class EncoderFfmpegCore : public Encoder {
EncoderCallback* m_pCallback;
TrackPointer m_pMetaData;

QString m_strMetaDataTitle;
QString m_strMetaDataArtist;
QString m_strMetaDataAlbum;
QFile m_pFile;

QByteArray m_strReadByteArray;
Expand Down
82 changes: 69 additions & 13 deletions src/encoder/encodermp3.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
#include "encoder/encodermp3.h"

#include <lame/lame.h>
#include <limits.h>
#include <qbytearrayview.h>
#include <qobject.h>
#include <qstringliteral.h>

#include <QByteArray>
#include <QObject>
#include <QtDebug>

#include "audio/types.h"
#include "encoder/encodercallback.h"
#include "encoder/encodermp3settings.h"
#include "util/assert.h"

namespace {
constexpr size_t kHeaderPadding = 10000;
}

Check warning on line 20 in src/encoder/encodermp3.cpp

View workflow job for this annotation

GitHub Actions / clang-tidy

anonymous namespace not terminated with a closing comment [google-readability-namespace-comments]

// Automatic thresholds for switching the encoder to mono
// They have been chosen by testing and to keep the same number
Expand Down Expand Up @@ -81,7 +91,7 @@ void EncoderMp3::setEncoderSettings(const EncoderSettings& settings) {
}
}

int EncoderMp3::bufferOutGrow(int size) {
size_t EncoderMp3::bufferOutGrow(size_t size) {
if (m_bufferOutSize >= size) {
return 0;
}
Expand All @@ -95,7 +105,7 @@ int EncoderMp3::bufferOutGrow(int size) {
return 0;
}

int EncoderMp3::bufferInGrow(int size) {
size_t EncoderMp3::bufferInGrow(size_t size) {
if (m_bufferInSize >= size) {
return 0;
}
Expand All @@ -117,7 +127,7 @@ void EncoderMp3::flush() {
return;
}
// Flush also writes ID3 tags.
int rc = lame_encode_flush(m_lameFlags, m_bufferOut, m_bufferOutSize);
int rc = lame_encode_flush(m_lameFlags, m_bufferOut, static_cast<int>(m_bufferOutSize));
if (rc < 0) {
return;
}
Expand All @@ -127,16 +137,45 @@ void EncoderMp3::flush() {
// `lame_get_lametag_frame` returns the number of bytes copied into buffer,
// or the required buffer size, if the provided buffer is too small.
// Function failed, if the return value is larger than `m_bufferOutSize`!
int numBytes = static_cast<int>(
lame_get_lametag_frame(m_lameFlags, m_bufferOut, m_bufferOutSize));
size_t numBytes = lame_get_lametag_frame(m_lameFlags, m_bufferOut, m_bufferOutSize);
if (numBytes > m_bufferOutSize) {
bufferOutGrow(numBytes);
numBytes = static_cast<int>(lame_get_lametag_frame(
m_lameFlags, m_bufferOut, m_bufferOutSize));
}
// Write the lame/xing header.

// Ideally, we should shift the file content forward to insert the header
// (ID3v2 & Xing frame) dynamically, but `EncoderCallback` doesn't support
// that so we use the static header padding and truncate the tracklist if
// too long
size_t tracklistMaxSize = kHeaderPadding - numBytes -
lame_get_id3v2_tag(m_lameFlags, nullptr, 0);
auto trackList = getTrackList().join("\n");
if (!trackList.isEmpty()) {
// Because of the static header size offset, we need to ensure that the
// tracklist comment won't make the header overflow on the MP3 frames,
// we we truncate to the max value
auto currentSize = static_cast<size_t>(trackList.size());
if (currentSize > tracklistMaxSize - 1) { // -1 since we need a byte for the NULL terminator
trackList = trackList.left(tracklistMaxSize - 4) + "...";
}
id3tag_set_comment(m_lameFlags, trackList.toLatin1());
}

size_t id3HeaderNumBytes = lame_get_id3v2_tag(m_lameFlags, nullptr, 0);
QByteArray id3Buffer(id3HeaderNumBytes, 0);

DEBUG_ASSERT(lame_get_id3v2_tag(m_lameFlags,
(unsigned char*)id3Buffer.data(),
id3HeaderNumBytes) == id3HeaderNumBytes);
DEBUG_ASSERT(id3Buffer.size() + numBytes <= kHeaderPadding);

m_pCallback->seek(0);
m_pCallback->write(nullptr, m_bufferOut, 0, numBytes);
// Write the lame/xing header.
m_pCallback->write((const unsigned char*)id3Buffer.constData(),
m_bufferOut,
static_cast<int>(id3Buffer.size()),
static_cast<int>(numBytes));
}

void EncoderMp3::encodeBuffer(const CSAMPLE *samples, const int size) {
Expand All @@ -158,8 +197,12 @@ void EncoderMp3::encodeBuffer(const CSAMPLE *samples, const int size) {
m_bufferIn[1][i] = samples[i*2+1] * SHRT_MAX;
}

rc = lame_encode_buffer_float(m_lameFlags, m_bufferIn[0], m_bufferIn[1],
size/2, m_bufferOut, m_bufferOutSize);
rc = lame_encode_buffer_float(m_lameFlags,
m_bufferIn[0],
m_bufferIn[1],
size / 2,
m_bufferOut,
static_cast<int>(m_bufferOutSize));
if (rc < 0) {
return;
}
Expand All @@ -173,6 +216,10 @@ void EncoderMp3::initStream() {

m_bufferIn[0] = (float *)malloc(m_bufferOutSize * sizeof(float));
m_bufferIn[1] = (float *)malloc(m_bufferOutSize * sizeof(float));

// Add a static header padding, which will be filled one termination
QByteArray headerPad(kHeaderPadding, 0);
m_pCallback->write(nullptr, (unsigned char*)headerPad.constData(), 0, headerPad.size());
}

int EncoderMp3::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) {
Expand Down Expand Up @@ -219,6 +266,8 @@ int EncoderMp3::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserE

//ID3 Tag if fields are not NULL
id3tag_init(m_lameFlags);
// ID3 tags will be written manually when flushing the encoder.
lame_set_write_id3tag_automatic(m_lameFlags, 0);
if (!m_metaDataTitle.isEmpty()) {
id3tag_set_title(m_lameFlags, m_metaDataTitle.toLatin1().constData());
}
Expand All @@ -240,8 +289,15 @@ int EncoderMp3::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserE
return 0;
}

void EncoderMp3::updateMetaData(const QString& artist, const QString& title, const QString& album) {
m_metaDataTitle = title;
m_metaDataArtist = artist;
m_metaDataAlbum = album;
void EncoderMp3::updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode) {
if (m_bufferOut == nullptr) {
m_metaDataTitle = title;
m_metaDataArtist = artist;
m_metaDataAlbum = album;
} else {
addToTracklist(artist, title, timecode);
}
}
13 changes: 8 additions & 5 deletions src/encoder/encodermp3.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ class EncoderMp3 final : public Encoder {

int initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) override;
void encodeBuffer(const CSAMPLE *samples, const int size) override;
void updateMetaData(const QString& artist, const QString& title, const QString& album) override;
void updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode = {}) override;
void flush() override;
void setEncoderSettings(const EncoderSettings& settings) override;

private:
void initStream();
int bufferOutGrow(int size);
int bufferInGrow(int size);
size_t bufferOutGrow(size_t size);
size_t bufferInGrow(size_t size);

lame_t m_lameFlags;
QString m_metaDataTitle;
Expand All @@ -35,9 +38,9 @@ class EncoderMp3 final : public Encoder {
vbr_mode m_encoding_mode;
MPEG_mode_e m_stereo_mode;
unsigned char *m_bufferOut;
int m_bufferOutSize;
size_t m_bufferOutSize;
float* m_bufferIn[2];
int m_bufferInSize;
size_t m_bufferInSize;

EncoderCallback* m_pCallback;
};
18 changes: 14 additions & 4 deletions src/encoder/encoderopus.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "encoder/encoderopus.h"

#include <qglobal.h>

#include <QByteArray>
#include <QMapIterator>
#include <QRandomGenerator>
Expand Down Expand Up @@ -460,10 +462,18 @@ void EncoderOpus::writePage(ogg_packet* pPacket) {
} while(!ogg_page_eos(&m_oggPage));
}

void EncoderOpus::updateMetaData(const QString& artist, const QString& title, const QString& album) {
m_opusComments.insert("ARTIST", artist);
m_opusComments.insert("TITLE", title);
m_opusComments.insert("ALBUM", album);
void EncoderOpus::updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode) {
// We assume all the base tags are added at the same time, so only check for ARTIST presence
if (!m_opusComments.contains("ARTIST")) {
m_opusComments.insert("ARTIST", artist);
m_opusComments.insert("TITLE", title);
m_opusComments.insert("ALBUM", album);
}
// Tracklist tag not supported in OPUS
Q_UNUSED(timecode);
}

void EncoderOpus::flush() {
Expand Down
5 changes: 4 additions & 1 deletion src/encoder/encoderopus.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class EncoderOpus: public Encoder {

int initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) override;
void encodeBuffer(const CSAMPLE *samples, const int size) override;
void updateMetaData(const QString& artist, const QString& title, const QString& album) override;
void updateMetaData(const QString& artist,
const QString& title,
const QString& album,
std::chrono::seconds timecode = {}) override;
void flush() override;
void setEncoderSettings(const EncoderSettings& settings) override;

Expand Down
Loading

0 comments on commit acc29fb

Please sign in to comment.