d7e5c59547
Fix the ProjectMedia refusal path's false bank claims and file relocation, an unreachable-undo idiom, and eight comment/doc accuracy issues.
105 lines
3.9 KiB
C++
105 lines
3.9 KiB
C++
// capture_name — pure implementation. See the header.
|
|
|
|
#include "core/capture/capture_name.h"
|
|
|
|
#include <cstdio>
|
|
|
|
namespace reasampler::capture {
|
|
|
|
namespace {
|
|
|
|
// A track name padded with spaces would render ragged in the label and as underscores in
|
|
// the stem, so both ends are trimmed before anything else looks at it.
|
|
std::string trimmed(const std::string& s) {
|
|
std::size_t b = 0;
|
|
std::size_t e = s.size();
|
|
auto isSpace = [](unsigned char c) {
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
};
|
|
while (b < e && isSpace(static_cast<unsigned char>(s[b]))) ++b;
|
|
while (e > b && isSpace(static_cast<unsigned char>(s[e - 1]))) --e;
|
|
return s.substr(b, e - b);
|
|
}
|
|
|
|
// Truncating mid-sequence would put invalid UTF-8 into the persisted label, so the cut
|
|
// backs off over continuation bytes (10xxxxxx). The stem does not care — sanitizeStem
|
|
// replaces every non-ASCII byte anyway — but one rule for both keeps them the same name.
|
|
std::string truncateUtf8(const std::string& s, std::size_t maxBytes) {
|
|
if (s.size() <= maxBytes) return s;
|
|
std::size_t cut = maxBytes;
|
|
while (cut > 0 && (static_cast<unsigned char>(s[cut]) & 0xC0) == 0x80) --cut;
|
|
return s.substr(0, cut);
|
|
}
|
|
|
|
int clampTo(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
|
|
|
} // namespace
|
|
|
|
std::string formatCaptureStamp(const CaptureStamp& stamp) {
|
|
if (stamp.month < 1 || stamp.day < 1) return {};
|
|
char buf[24];
|
|
std::snprintf(buf, sizeof(buf), "%02d-%02d %02d%02d",
|
|
clampTo(stamp.month, 1, 12), clampTo(stamp.day, 1, 31),
|
|
clampTo(stamp.hour, 0, 23), clampTo(stamp.minute, 0, 59));
|
|
return buf;
|
|
}
|
|
|
|
CaptureName composeCaptureName(const CaptureNameInputs& in) {
|
|
std::string base;
|
|
int named = 0;
|
|
for (const std::string& raw : in.sourceNames) {
|
|
const std::string n = trimmed(raw);
|
|
if (n.empty()) continue;
|
|
if (base.empty()) base = n;
|
|
++named;
|
|
}
|
|
if (base.empty()) base = trimmed(in.fallback);
|
|
if (base.empty()) base = "capture";
|
|
base = truncateUtf8(base, kMaxSourceNameBytes);
|
|
// truncateUtf8 backs off over continuation bytes, so a name whose first kMaxSourceNameBytes
|
|
// bytes are ALL continuation bytes (0x80-0xBF) backs off to nothing — re-apply the "never an
|
|
// empty label" fallback after truncation, not just before it.
|
|
if (base.empty()) base = "capture";
|
|
|
|
CaptureName out;
|
|
out.label = base;
|
|
out.stemBase = base;
|
|
|
|
// Several sources collapse onto the first one's name plus a count of the rest — the
|
|
// alternative (joining every name) produces a stem no one can read and a label that
|
|
// no longer fits a card.
|
|
if (named > 1) {
|
|
const std::string extra = std::to_string(named - 1);
|
|
out.label += " +" + extra;
|
|
out.stemBase += "+" + extra;
|
|
}
|
|
|
|
if (in.ordinal > 0) {
|
|
const std::string ord = std::to_string(in.ordinal);
|
|
out.label += " #" + ord;
|
|
out.stemBase += "-" + ord;
|
|
}
|
|
|
|
const std::string stamp = formatCaptureStamp(in.stamp);
|
|
if (!stamp.empty()) out.label += " " + stamp;
|
|
|
|
return out;
|
|
}
|
|
|
|
std::string captureTrackName(const std::string& sourceName) {
|
|
const std::string prefix(kCaptureTrackPrefix);
|
|
// A source with no readable name yields the bare word rather than a trailing
|
|
// space; both spellings are fixed points, which is what makes the whole function
|
|
// one (a track named exactly "Capture" must not become "Capture Capture"). Read
|
|
// from kCaptureTrackPrefixBare rather than chopped off prefix, so the two names
|
|
// can't drift out of sync with each other (both expand from the same header token).
|
|
const std::string bare = kCaptureTrackPrefixBare;
|
|
|
|
if (sourceName.empty()) return bare;
|
|
if (sourceName == bare) return sourceName;
|
|
if (sourceName.rfind(prefix, 0) == 0) return sourceName;
|
|
return prefix + sourceName;
|
|
}
|
|
|
|
} // namespace reasampler::capture
|