Files
homeclaw/web-ui/audiomass/id3.js
T
kimandClaude Opus 5 e85ebe6608 v4.3.20: 미커밋 자산 일괄 정리 + 유저 워크스페이스/세션토큰 gitignore
그동안 쌓여 있던 미커밋 파일들을 정리. 소스·자산만 올리고, 개인 데이터와
런타임 상태는 gitignore로 차단.

추가된 자산:
- web-ui/audiomass, web-ui/wavacity — 자체 호스팅 오디오 에디터
- web-ui/drums — 드럼 샘플
- wol-gate — WOL 프록시 컨테이너(Dockerfile + server.js)
- glm-1m-fix — GLM 1M 컨텍스트 설치 스크립트(linux/windows)
- RENODE_FIXES_SUMMARY*.md, CCTV정보_서울특별시.csv, .claude/plans

수정:
- edge_tts_synth.py: rate 인자 추가(말하기 속도 조절)
- .smallclaw/config.json: google(gemini) provider 등록 — 키는 vault 참조
- assets/SmallClaw*.png 삭제분 반영

gitignore (중요):
- .smallclaw/users/*/workspace/ 전체 차단. 기존 패턴이 memory/uploads와 일부
  확장자만 막아서 detective/(고소장·통화녹취·의사소견서·가족관계증명서),
  generated-media/, music/, pptx/ 등 앱 생성 개인 데이터 403MB가 노출돼 있었음
- .smallclaw/active-sessions.json 차단 — 살아있는 bearer 토큰과 admin 역할이
  평문으로 들어있어 커밋 시 게이트웨이 관리자 자격증명이 그대로 공개됨
- google-usage.json, config.json.bak-*, workspace/memory·weather-maps·
  task_result_*, voice/ 등 런타임 상태도 함께 차단

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:55:37 +09:00

636 lines
22 KiB
JavaScript

(function ( w, d, PKAE ) {
'use strict';
var StringUtils = {
readUTF16String: function(bytes, bigEndian, maxBytes) {
var ix = 0;
var offset1 = 1, offset2 = 0;
maxBytes = Math.min(maxBytes||bytes.length, bytes.length);
if( bytes[0] == 0xFE && bytes[1] == 0xFF ) {
bigEndian = true;
ix = 2;
} else if( bytes[0] == 0xFF && bytes[1] == 0xFE ) {
bigEndian = false;
ix = 2;
}
if( bigEndian ) {
offset1 = 0;
offset2 = 1;
}
var arr = [];
for( var j = 0; ix < maxBytes; j++ ) {
var byte1 = bytes[ix+offset1];
var byte2 = bytes[ix+offset2];
var word1 = (byte1<<8)+byte2;
ix += 2;
if( word1 == 0x0000 ) {
break;
} else if( byte1 < 0xD8 || byte1 >= 0xE0 ) {
arr[j] = String.fromCharCode(word1);
} else {
var byte3 = bytes[ix+offset1];
var byte4 = bytes[ix+offset2];
var word2 = (byte3<<8)+byte4;
ix += 2;
arr[j] = String.fromCharCode(word1, word2);
}
}
var string = new String(arr.join(""));
string.bytesReadCount = ix;
return string;
},
readUTF8String: function(bytes, maxBytes) {
var ix = 0;
maxBytes = Math.min(maxBytes||bytes.length, bytes.length);
if( bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF ) {
ix = 3;
}
var arr = [];
for( var j = 0; ix < maxBytes; j++ ) {
var byte1 = bytes[ix++];
if( byte1 == 0x00 ) {
break;
} else if( byte1 < 0x80 ) {
arr[j] = String.fromCharCode(byte1);
} else if( byte1 >= 0xC2 && byte1 < 0xE0 ) {
var byte2 = bytes[ix++];
arr[j] = String.fromCharCode(((byte1&0x1F)<<6) + (byte2&0x3F));
} else if( byte1 >= 0xE0 && byte1 < 0xF0 ) {
var byte2 = bytes[ix++];
var byte3 = bytes[ix++];
arr[j] = String.fromCharCode(((byte1&0xFF)<<12) + ((byte2&0x3F)<<6) + (byte3&0x3F));
} else if( byte1 >= 0xF0 && byte1 < 0xF5) {
var byte2 = bytes[ix++];
var byte3 = bytes[ix++];
var byte4 = bytes[ix++];
var codepoint = ((byte1&0x07)<<18) + ((byte2&0x3F)<<12)+ ((byte3&0x3F)<<6) + (byte4&0x3F) - 0x10000;
arr[j] = String.fromCharCode(
(codepoint>>10) + 0xD800,
(codepoint&0x3FF) + 0xDC00
);
}
}
var string = new String(arr.join(""));
string.bytesReadCount = ix;
return string;
},
readNullTerminatedString: function(bytes, maxBytes) {
var arr = [];
maxBytes = maxBytes || bytes.length;
for ( var i = 0; i < maxBytes; ) {
var byte1 = bytes[i++];
if( byte1 == 0x00 ) break;
arr[i-1] = String.fromCharCode(byte1);
}
var string = new String(arr.join(""));
string.bytesReadCount = i;
return string;
}
};
var getBytesAt = function(data, iOffset, iLength) {
if (iOffset < 0 || iLength < 0 || iOffset + iLength > data.byteLength) return [];
var bytes = new Array(iLength);
for( var i = 0; i < iLength; i++ ) {
bytes[i] = data.getUint8(iOffset+i);
}
return bytes;
};
var getStringWithCharsetAt = function(data, iOffset, iLength, iCharset) {
var bytes = getBytesAt(data, iOffset, iLength);
var sString;
switch( (iCharset || '').toString().toLowerCase() ) {
case 'utf-16':
case 'utf-16le':
case 'utf-16be':
sString = StringUtils.readUTF16String(bytes, iCharset);
break;
case 'utf-8':
sString = StringUtils.readUTF8String(bytes);
break;
default:
sString = StringUtils.readNullTerminatedString(bytes);
break;
}
return sString;
};
var ID3v2 = {
readFrameData: {}
};
var getStringAt = function(data, iOffset, iLength) {
if (iOffset < 0 || iLength < 0 || iOffset + iLength > data.byteLength) return '';
var aStr = [];
for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
aStr[j] = String.fromCharCode(data.getUint8(i));
}
return aStr.join("");
};
var getLongAt = function(data, iOffset, bBigEndian) {
var iByte1 = data.getUint8(iOffset),
iByte2 = data.getUint8(iOffset + 1),
iByte3 = data.getUint8(iOffset + 2),
iByte4 = data.getUint8(iOffset + 3);
var iLong = bBigEndian ?
(((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
: (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
if (iLong < 0) iLong += 4294967296;
return iLong;
};
var getShortAt = function(data, iOffset, bBigEndian) {
var iShort = bBigEndian ?
(data.getUint8(iOffset) << 8) + data.getUint8(iOffset + 1)
: (data.getUint8(iOffset + 1) << 8) + data.getUint8(iOffset);
if (iShort < 0) iShort += 65536;
return iShort;
};
var getInteger24At = function(data, iOffset, bBigEndian) {
var iByte1 = data.getUint8(iOffset),
iByte2 = data.getUint8(iOffset + 1),
iByte3 = data.getUint8(iOffset + 2);
var iInteger = bBigEndian ?
((((iByte1 << 8) + iByte2) << 8) + iByte3)
: ((((iByte3 << 8) + iByte2) << 8) + iByte1);
if (iInteger < 0) iInteger += 16777216;
return iInteger;
};
var isBitSetAt = function ( dataview, offset, bit ) {
var ibyte = dataview.getUint8(offset);
return (ibyte & (1 << bit)) != 0;
};
var readSynchsafeInteger32At = function (offset, data) {
var size1 = data.getUint8(offset);
var size2 = data.getUint8(offset+1);
var size3 = data.getUint8(offset+2);
var size4 = data.getUint8(offset+3);
// 0x7f = 0b01111111
var size = size4 & 0x7f
| ((size3 & 0x7f) << 7)
| ((size2 & 0x7f) << 14)
| ((size1 & 0x7f) << 21);
return size;
};
var readFrameFlags = function(data, offset) {
return {
format: {
unsynchronisation: isBitSetAt(data, offset+1, 1),
data_length_indicator: isBitSetAt(data, offset+1, 0)
}
};
};
var _shortcuts = {
"title" : ["TIT2", "TT2"],
"artist" : ["TPE1", "TP1"],
"album" : ["TALB", "TAL"],
"year" : ["TDRC", "TYER", "TYE"],
"comment" : ["COMM", "COM"],
"track" : ["TRCK", "TRK"],
"genre" : ["TCON", "TCO"],
"picture" : ["APIC", "PIC"],
"lyrics" : ["USLT", "ULT"]
};
var _defaultShortcuts = ["title", "artist", "album", "year", "comment", "track", "genre", "picture", "lyrics"];
var getTagsFromShortcuts = function(shortcuts) {
var tags = [];
for( var i = 0, shortcut; shortcut = shortcuts[i]; i++ ) {
tags = tags.concat(_shortcuts[shortcut]||[shortcut]);
}
return tags;
};
var getFrameData = function( frames, ids ) {
if( typeof ids == 'string' ) { ids = [ids]; }
for( var i = 0, id; id = ids[i]; i++ ) {
if( id in frames ) { return frames[id]; }
}
};
var readFrames = function (offset, end, data, id3header, tags) {
var frames = {};
var major = id3header["major"];
tags = getTagsFromShortcuts(tags || _defaultShortcuts);
end = Math.min(end, data.byteLength);
while( offset < end ) {
var readFrameFunc = null;
var frameData = data;
var frameDataOffset = offset;
var flags = null;
switch( major ) {
case 2:
if (frameDataOffset + 6 > end) return frames;
var frameID = getStringAt(frameData, frameDataOffset, 3);
var frameSize = getInteger24At(frameData, frameDataOffset+3, true);
var frameHeaderSize = 6;
break;
case 3:
if (frameDataOffset + 10 > end) return frames;
var frameID = getStringAt(frameData, frameDataOffset, 4);
var frameSize = getLongAt(frameData, frameDataOffset+4, true);
var frameHeaderSize = 10;
break;
case 4:
if (frameDataOffset + 10 > end) return frames;
var frameID = getStringAt(frameData, frameDataOffset, 4);
var frameSize = readSynchsafeInteger32At(frameDataOffset+4, frameData);
var frameHeaderSize = 10;
break;
}
// if last frame GTFO
if( frameID == "" ) { break; }
if( !frameSize || frameDataOffset + frameHeaderSize + frameSize > end ) { break; }
// advance data offset to the next frame data
offset += frameHeaderSize + frameSize;
// skip unwanted tags
if( tags.indexOf( frameID ) < 0 ) { continue; }
// read frame message and format flags
if( major > 2 )
{
flags = readFrameFlags(frameData, frameDataOffset+8);
}
frameDataOffset += frameHeaderSize;
// the first 4 bytes are the real data size
// (after unsynchronisation && encryption)
if( flags && flags.format.data_length_indicator )
{
frameDataOffset += 4;
frameSize -= 4;
}
// TODO: support unsynchronisation
if( flags && flags.format.unsynchronisation )
{
//frameData = removeUnsynchronisation(frameData, frameSize);
continue;
}
// find frame parsing function
if( frameID in ID3v2.readFrameData ) {
readFrameFunc = ID3v2.readFrameData[frameID];
} else if( frameID[0] == "T" ) {
readFrameFunc = ID3v2.readFrameData["T*"];
}
var parsedData = readFrameFunc ? readFrameFunc(frameDataOffset, frameSize, frameData, flags) : undefined;
if( !(frameID in frames) ) {
frames[frameID] = parsedData;
}
}
return frames;
};
function getTextEncoding( bite ) {
var charset;
switch( bite )
{
case 0x00:
charset = 'iso-8859-1';
break;
case 0x01:
charset = 'utf-16';
break;
case 0x02:
charset = 'utf-16be';
break;
case 0x03:
charset = 'utf-8';
break;
}
return charset;
}
ID3v2.readFrameData['APIC'] = function readPictureFrame(offset, length, data, flags, v) {
v = v || '3';
var start = offset;
var charset = getTextEncoding( data.getUint8(offset) );
switch( v ) {
case '2':
var format = getStringAt(data, offset+1, 3);
offset += 4;
break;
case '3':
case '4':
var format = getStringWithCharsetAt(data, offset+1, length - (offset-start), '');
offset += 1 + format.bytesReadCount;
break;
}
var bite = data.getUint8(offset, 1);
var desc = getStringWithCharsetAt(data, offset+1, length - (offset-start), charset);
offset += 1 + desc.bytesReadCount;
return {
"format" : format.toString(),
"type" : bite,
"description" : desc.toString(),
"data" : getBytesAt(data, offset, (start+length) - offset)
};
};
ID3v2.readFrameData['COMM'] = function readCommentsFrame(offset, length, data) {
var start = offset;
var charset = getTextEncoding( data.getUint8(offset) );
var language = getStringAt(data, offset+1, 3 );
var shortdesc = getStringWithCharsetAt(data, offset+4, length-4, charset);
offset += 4 + shortdesc.bytesReadCount;
var text = getStringWithCharsetAt(data, offset, (start+length) - offset, charset );
return {
language : language,
short_description : shortdesc.toString(),
text : text.toString()
};
};
ID3v2.readFrameData['COM'] = ID3v2.readFrameData['COMM'];
ID3v2.readFrameData['PIC'] = function(offset, length, data, flags) {
return ID3v2.readFrameData['APIC'](offset, length, data, flags, '2');
};
ID3v2.readFrameData['T*'] = function readTextFrame(offset, length, data) {
var charset = getTextEncoding( data.getUint8(offset) );
return getStringWithCharsetAt(data, offset+1, length-1, charset).toString();
};
ID3v2.readFrameData['TCON'] = function readGenreFrame(offset, length, data) {
var text = ID3v2.readFrameData['T*'].apply( this, arguments );
return text.replace(/^\(\d+\)/, '');
};
ID3v2.readFrameData['TCO'] = ID3v2.readFrameData['TCON'];
ID3v2.readFrameData['USLT'] = function readLyricsFrame(offset, length, data) {
var start = offset;
var charset = getTextEncoding( data.getUint8(offset) );
var language = getStringAt(data, offset+1, 3 );
var descriptor = getStringWithCharsetAt(data, offset+4, length-4, charset );
offset += 4 + descriptor.bytesReadCount;
var lyrics = getStringWithCharsetAt(data, offset, (start+length) - offset, charset );
return {
language : language,
descriptor : descriptor.toString(),
lyrics : lyrics.toString()
};
};
ID3v2.readFrameData['ULT'] = ID3v2.readFrameData['USLT'];
ID3v2.ReadTags = function ( arraybuffer ) {
if (!arraybuffer || arraybuffer.byteLength < 10) return null;
var data = new DataView ( arraybuffer );
var offset = 0;
if (getStringAt(data, 0, 3) !== 'ID3') return null;
var major = data.getUint8(offset+3);
if( major < 2 || major > 4 ) { return null; }
var unsynch = isBitSetAt(data, offset+5, 7);
var xheader = isBitSetAt(data, offset+5, 6);
var size = readSynchsafeInteger32At(offset+6, data);
var end = offset + 10 + size;
if (end > data.byteLength) return null;
offset += 10;
if( xheader ) {
if (offset + 4 > end) return null;
var xheadersize = data.getInt32( offset, true ); //data.getLongAt(offset, true);
// The 'Extended header size', currently 6 or 10 bytes, excludes itself.
offset += xheadersize + 4;
if (offset > end) return null;
}
var id3 = {};
var frames = unsynch ? {} : readFrames(offset, end, data, { major: major });
// create shortcuts for most common data
for( var name in _shortcuts ) if(_shortcuts.hasOwnProperty(name)) {
var data = getFrameData( frames, _shortcuts[name] );
if( data ) id3[name] = data;
}
return id3;
};
var _s = function (s) {
for (var a = [], i = 0; i < s.length; ++i) a[i] = s.charCodeAt(i) & 255;
return a;
};
var _n = function (n) {
return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255];
};
var _ss = function (n) {
return [(n >>> 21) & 127, (n >>> 14) & 127, (n >>> 7) & 127, n & 127];
};
var _u = function (s, bom) {
s = (s || '').toString();
for (var a = bom ? [255, 254] : [], i = 0, c; i < s.length; ++i) {
c = s.charCodeAt(i); a.push(c & 255, c >> 8);
}
return a;
};
var _v = function (v, k) {
return (v && v[k] !== undefined) ? v[k] : (v || '');
};
var _f = function (id, b) {
return b && b.length ? _s(id).concat(_n(b.length), [0, 0], b) : [];
};
var _at = function (ab) {
var b = new Uint8Array(ab);
if (b.length < 10 || b[0] != 73 || b[1] != 68 || b[2] != 51) return 0;
var n = 10 + ((b[6] & 127) << 21 | (b[7] & 127) << 14 | (b[8] & 127) << 7 | (b[9] & 127)) + ((b[5] & 16) ? 10 : 0);
return n > b.length ? 0 : n;
};
var _apic = function (p) {
if (!p || !p.data || !p.data.length) return [];
var d = p.data, a = [0].concat(_s(p.format || 'image/jpeg'), [0, p.type || 3, 0]);
for (var i = 0; i < d.length; ++i) a.push(d[i]);
return a;
};
var _txt = function (s) {
s = (s || '').toString();
return s ? [1].concat(_u(s, 1)) : [];
};
ID3v2.WriteTags = function (ab, tag) {
var fr = [], p = tag.picture;
fr = fr.concat(
_f('TIT2', _txt(tag.title)),
_f('TPE1', _txt(tag.artist)),
_f('TALB', _txt(tag.album)),
_f('TYER', _txt(tag.year)),
_f('TCON', _txt(tag.genre)),
_f('TRCK', _txt(tag.track)),
_f('COMM', _v(tag.comment, 'text') ? [1].concat(_s('eng'), _u('', 1), [0, 0], _u(_v(tag.comment, 'text'), 1)) : []),
_f('USLT', _v(tag.lyrics, 'lyrics') ? [1].concat(_s('eng'), _u('', 1), [0, 0], _u(_v(tag.lyrics, 'lyrics'), 1)) : []),
_f('APIC', _apic(p))
);
var hd = _s('ID3').concat([3, 0, 0], _ss(fr.length));
var au = new Uint8Array(ab, _at(ab));
var out = new Uint8Array(hd.length + fr.length + au.length), o = 0;
out.set(hd, o); o += hd.length;
out.set(fr, o); o += fr.length;
out.set(au, o);
return out.buffer;
};
w.ID3v2 = ID3v2;
/// -------
var ID4 = {};
ID4.types = {
'0' : 'uint8',
'1' : 'text',
'13' : 'jpeg',
'14' : 'png',
'21' : 'uint8'
};
ID4.atom = {
'©alb': ['album'],
'©art': ['artist'],
'©ART': ['artist'],
'aART': ['artist'],
'©day': ['year'],
'©nam': ['title'],
'©gen': ['genre'],
'trkn': ['track'],
'covr': ['picture'],
'©lyr': ['lyrics'],
'©cmt': ['comment']
};
ID4.ReadTags = function(arraybuffer) {
if (!arraybuffer || arraybuffer.byteLength < 8) return null;
var data = new DataView ( arraybuffer );
var tag = {};
readAtom(tag, data, 0, data.byteLength);
return tag;
};
function readAtom(tag, data, offset, length, indent)
{
indent = indent === undefined ? "" : indent + " ";
var seek = offset;
var end = Math.min(offset + length, data.byteLength);
while (seek + 8 <= end)
{
var atomSize = data.getInt32(seek); // getLongAt(data, seek, true);
if (atomSize == 0) return;
if (atomSize < 8 || seek + atomSize > end) return;
var atomName = getStringAt(data, seek + 4, 4);
// Container atoms
if (atomName === 'meta')
{
seek += 4; // next_item_id (uint32)
readAtom(tag, data, seek + 8, atomSize - 8, indent);
return;
}
if (atomName === 'moov' || atomName === 'udta' || atomName === 'ilst' ) // ['moov', 'udta', 'meta', 'ilst'].indexOf(atomName) > -1)
{
readAtom(tag, data, seek + 8, atomSize - 8, indent);
return;
}
/*
if (['moov', 'udta', 'meta', 'ilst'].indexOf(atomName) > -1)
{
if (atomName === 'meta') seek += 4; // next_item_id (uint32)
readAtom(tag, data, seek + 8, atomSize - 8, indent);
return;
}
*/
// Value atoms
if (ID4.atom[atomName])
{
if (seek + 24 > end) return;
var klass = getInteger24At(data, seek + 16 + 1, true);
var atom = ID4.atom[atomName];
var type = ID4.types[klass];
if (atomName === 'trkn')
{
if (seek + 29 > end) return;
tag[atom[0]] = data.getUint8(seek + 16 + 11);
tag['count'] = data.getUint8(seek + 16 + 13);
}
else
{
// 16: name + size + "data" + size (4 bytes each)
// 4: atom version (1 byte) + atom flags (3 bytes)
// 4: NULL (usually locale indicator)
var dataStart = seek + 16 + 4 + 4;
var dataEnd = atomSize - 16 - 4 - 4;
if (dataEnd < 0 || dataStart + dataEnd > end) return;
var atomData;
switch( type ) {
case 'text':
atomData = getStringWithCharsetAt(data, dataStart, dataEnd, "UTF-8");
break;
case 'uint8':
atomData = getShortAt(data, dataStart);
break;
case 'jpeg':
case 'png':
atomData = {
format : "image/" + type,
data : getBytesAt(data, dataStart, dataEnd)
};
break;
}
if (atom[0] === "comment") {
tag[atom[0]] = {
"text": atomData
};
} else {
tag[atom[0]] = atomData;
}
}
}
seek += atomSize;
}
}
w.ID4 = ID4;
})( window, document, PKAudioEditor );