This commit is contained in:
Robin E.R. Davies
2026-02-04 15:05:37 -05:00
parent f0c3995a8f
commit e5beb97466
35 changed files with 3815 additions and 317 deletions
+68 -3
View File
@@ -38,13 +38,78 @@ function getVersionSuffix(filePath: string): string | null {
return match ? match[1] : null;
}
export function safeFilenameDecode(filename: string): string {
let originalFilename = filename;
let pos = filename.indexOf('%');
if (pos < 0) {
return filename;
}
let result = "";
while (true) {
if (pos < 0) {
result += filename;
return result;
}
result += filename.substring(0, pos);
if (pos+3 >= filename.length) {
// invalid encoding. just use the original
return originalFilename;
}
let hex = filename.charAt(pos+1) + filename.charAt(pos+2);
let byte = parseInt(hex, 16);
// Handle UTF-8 to UTF-16 conversion
if (byte < 0x80) {
// Single-byte character (ASCII)
result += String.fromCharCode(byte);
filename = filename.substring(pos+3);
} else if ((byte & 0xE0) === 0xC0) {
// 2-byte sequence
if (pos+6 > filename.length || filename.charAt(pos+3) !== '%') {
return originalFilename; // invalid encoding
}
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
let codePoint = ((byte & 0x1F) << 6) | (byte2 & 0x3F);
result += String.fromCharCode(codePoint);
filename = filename.substring(pos+6);
} else if ((byte & 0xF0) === 0xE0) {
// 3-byte sequence
if (pos+9 > filename.length || filename.charAt(pos+3) !== '%' || filename.charAt(pos+6) !== '%') {
return originalFilename; // invalid encoding
}
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
let byte3 = parseInt(filename.charAt(pos+7) + filename.charAt(pos+8), 16);
let codePoint = ((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F);
result += String.fromCharCode(codePoint);
filename = filename.substring(pos+9);
} else if ((byte & 0xF8) === 0xF0) {
// 4-byte sequence (surrogate pair needed)
if (pos+12 > filename.length || filename.charAt(pos+3) !== '%' || filename.charAt(pos+6) !== '%' || filename.charAt(pos+9) !== '%') {
return originalFilename; // invalid encoding
}
let byte2 = parseInt(filename.charAt(pos+4) + filename.charAt(pos+5), 16);
let byte3 = parseInt(filename.charAt(pos+7) + filename.charAt(pos+8), 16);
let byte4 = parseInt(filename.charAt(pos+10) + filename.charAt(pos+11), 16);
let codePoint = ((byte & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
// Convert to UTF-16 surrogate pair
codePoint -= 0x10000;
result += String.fromCharCode(0xD800 + (codePoint >> 10));
result += String.fromCharCode(0xDC00 + (codePoint & 0x3FF));
filename = filename.substring(pos+12);
} else {
// Invalid UTF-8 start byte
return originalFilename;
}
pos = filename.indexOf('%');
}
}
export function getTrackTitle(pathname: string, metadata: AudioFileMetadata | null | undefined): string {
if (!metadata) {
return pathFileName(pathname);
return safeFilenameDecode(pathFileName(pathname));
}
if (metadata.title === "") {
return pathFileName(pathname);
return safeFilenameDecode(pathFileName(pathname));
}
let trackDisplay = "";
if (metadata.track > 0) {
@@ -56,7 +121,7 @@ export function getTrackTitle(pathname: string, metadata: AudioFileMetadata | nu
}
let result = trackDisplay + metadata.title;
if (result === "") {
result = pathFileName(pathname);
result = safeFilenameDecode(pathFileName(pathname));
}
let versionSuffix = getVersionSuffix(pathname);
if (versionSuffix) {