Last active
January 24, 2025 00:19
-
-
Save azihassan/85a7bb8c49ff23fe4d8cf7cb9fdfc8c4 to your computer and use it in GitHub Desktop.
Removes all groups from a CLX animation file except the first
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cassert> | |
#include <numeric> | |
#include <iostream> | |
#include <fstream> | |
#include <vector> | |
#include <set> | |
#include <functional> | |
#include <cstdint> | |
uint16_t readLittleEndianWord(std::ifstream& stream) | |
{ | |
uint8_t bytes[2] = {0}; | |
stream.read(reinterpret_cast<char*>(&bytes[1]), sizeof(uint8_t)); | |
stream.read(reinterpret_cast<char*>(&bytes[0]), sizeof(uint8_t)); | |
return bytes[1] | (bytes[0] << 8); | |
} | |
uint32_t readLittleEndianDoubleWord(std::ifstream& stream) | |
{ | |
uint8_t bytes[4] = {0}; | |
stream.read(reinterpret_cast<char*>(&bytes[3]), sizeof(uint8_t)); | |
stream.read(reinterpret_cast<char*>(&bytes[2]), sizeof(uint8_t)); | |
stream.read(reinterpret_cast<char*>(&bytes[1]), sizeof(uint8_t)); | |
stream.read(reinterpret_cast<char*>(&bytes[0]), sizeof(uint8_t)); | |
return bytes[3] | bytes[2] << 8 | bytes[1] << 16 | bytes[0] << 24; | |
} | |
void writeLittleEndianWord(std::ofstream& stream, uint16_t value) | |
{ | |
for(int i = 0; i < 2; i++) | |
{ | |
uint8_t byte = value & 0xff; | |
stream.write(reinterpret_cast<char*>(&byte), sizeof(uint8_t)); | |
value >>= 8; | |
} | |
} | |
void writeLittleEndianDoubleWord(std::ofstream& stream, uint32_t value) | |
{ | |
for(int i = 0; i < 4; i++) | |
{ | |
uint8_t byte = value & 0xff; | |
stream.write(reinterpret_cast<char*>(&byte), sizeof(uint8_t)); | |
value >>= 8; | |
} | |
} | |
struct Frame | |
{ | |
uint32_t offset; | |
std::vector<uint8_t> image; | |
Frame(uint32_t offset, std::vector<uint8_t> image) : offset(offset), image(image) {} | |
uint16_t headerSize() const | |
{ | |
return image[0] << 8 | image[1]; | |
} | |
uint16_t width() const | |
{ | |
return image[2] << 8 | image[3]; | |
} | |
uint16_t height() const | |
{ | |
return image[4] << 8 | image[5]; | |
} | |
uint32_t size() const | |
{ | |
return image.size(); | |
} | |
}; | |
struct Clip | |
{ | |
uint32_t offset; | |
uint32_t nextOffset; | |
uint32_t frameCount; | |
std::vector<uint32_t> frameOffsets; | |
std::vector<Frame> frames; | |
uint32_t headerSize() const | |
{ | |
return sizeof(frameCount) | |
+ sizeof(uint32_t) * frames.size() | |
+ sizeof(nextOffset); | |
} | |
uint32_t size() const | |
{ | |
assert(frameCount == frameOffsets.size()); | |
uint32_t frameSizes = 0; | |
for(size_t i = 0; i < frames.size(); i++) | |
{ | |
Frame frame = frames[i]; | |
assert(frameSizes + frame.size() > frameSizes); | |
frameSizes += frame.size(); | |
} | |
return headerSize() + frameSizes; | |
} | |
}; | |
class CLX | |
{ | |
public: | |
std::vector<uint32_t> groupOffsets; | |
std::vector<Clip> clips; | |
CLX(std::ifstream& stream) | |
{ | |
groupOffsets = findGroupOffsets(stream); | |
clips = findClips(stream, groupOffsets); | |
} | |
CLX(const std::string& filename) | |
{ | |
std::ifstream stream(filename, std::ios::binary); | |
groupOffsets = findGroupOffsets(stream); | |
clips = findClips(stream, groupOffsets); | |
assert(groupOffsets.size() == clips.size()); | |
for(size_t i = 0; i < clips.size(); i++) | |
{ | |
Clip clip = clips[i]; | |
//printf("Clip #%d at offset %d (0x%x) with size %d bytes\n", i, clip.offset, clip.offset, clip.size()); | |
assert(clip.frameCount == clip.frames.size()); | |
for(size_t j = 0; j < clip.frameCount; j++) | |
{ | |
Frame frame = clip.frames[j]; | |
//printf(" Frame #%d at offset %d (0x%x) and with size of %d bytes\n", j, frame.offset, frame.offset, frame.size()); | |
} | |
} | |
} | |
bool isMonoGroup() const | |
{ | |
return groupOffsets.size() == 1; | |
} | |
uint32_t fileSize() const | |
{ | |
return clips[clips.size() - 1].offset + clips[clips.size() - 1].nextOffset; | |
} | |
void removeGroup(size_t groupIndex) | |
{ | |
assert(groupIndex < groupOffsets.size()); | |
groupOffsets.erase(groupOffsets.begin() + groupIndex); | |
clips.erase(clips.begin() + groupIndex); | |
recalculateOffsets(); | |
} | |
void recalculateOffsets() | |
{ | |
if(groupOffsets.size() == 1) | |
{ | |
//monogroup, clip starts at offset 0 | |
clips[0].offset = 0; | |
clips[0].nextOffset = clips[0].size(); | |
} | |
else | |
{ | |
//first clip comes after the group offset list | |
clips[0].offset = groupOffsets.size() * sizeof(uint32_t); | |
clips[0].nextOffset = clips[0].size(); | |
} | |
for(size_t i = 1; i < clips.size(); i++) | |
{ | |
//second clip comes after the first clip + its frames | |
clips[i].offset = clips[i - 1].offset + clips[i - 1].size(); | |
clips[i].nextOffset = clips[i].size(); //nextOffset is relative to current offset | |
} | |
for(size_t i = 0; i < groupOffsets.size(); i++) | |
{ | |
groupOffsets[i] = clips[i].offset; | |
} | |
//frames come after their clip header (clip offset + clip header size) | |
//clips[0].frames[0].offset = clips[0].offset + clips[0].headerSize(); | |
size_t i = 0; | |
for(Clip& clip: clips) | |
{ | |
//printf("Clip #%d at offset %d (0x%x) with size %d bytes\n", i, clip.offset, clip.offset, clip.size()); | |
clip.frames[0].offset = clip.offset + clip.headerSize(); | |
//printf(" Frame #%d at offset %d (0x%x) and with size of %d bytes\n", 0, clip.frames[0].offset, clip.frames[0].offset, clip.frames[0].size()); | |
clip.frameOffsets[0] = clip.frames[0].offset; | |
for(size_t f = 1; f < clip.frames.size(); f++) | |
{ | |
clip.frames[f].offset = clip.frames[f - 1].offset + clip.frames[f - 1].image.size(); | |
clip.frameOffsets[f] = clip.frames[f].offset; | |
//printf(" Frame #%d at offset %d (0x%x) and with size of %d bytes\n", f, clip.frames[f].offset, clip.frames[f].offset, clip.frames[f].size()); | |
} | |
i++; | |
} | |
} | |
private: | |
std::vector<uint32_t> findGroupOffsets(std::ifstream& stream) | |
{ | |
std::vector<uint32_t> groupOffsets; | |
uint32_t fileSize; | |
stream.seekg(0, std::ios::end); | |
fileSize = stream.tellg(); | |
stream.seekg(0, std::ios::beg); | |
std::cout << "File size = " << fileSize << std::endl; | |
uint32_t groupFrameCountOrFirstOffset = readLittleEndianDoubleWord(stream); | |
//printf("groupFrameCountOrFirstOffset = %d (0x%x)\n", groupFrameCountOrFirstOffset, groupFrameCountOrFirstOffset); | |
stream.seekg(groupFrameCountOrFirstOffset * sizeof(uint32_t) + sizeof(uint32_t)); | |
uint32_t nextGroupOffset = readLittleEndianDoubleWord(stream); | |
//printf("nextGroupOffset = %d (0x%x)\n", nextGroupOffset, nextGroupOffset); | |
if(fileSize == nextGroupOffset) //monogroup | |
{ | |
groupOffsets.push_back(0); | |
std::cout << "Monogroup, adding artificial group offset of 0" << std::endl; | |
return groupOffsets; | |
} | |
//groupFrameCountOrFirstOffset is the first group offset after all | |
stream.seekg(0, std::ios::beg); | |
while(stream.tellg() != groupFrameCountOrFirstOffset) | |
{ | |
uint32_t groupOffset = readLittleEndianDoubleWord(stream); | |
groupOffsets.push_back(groupOffset); | |
} | |
return groupOffsets; | |
} | |
std::vector<Frame> findFrames(std::ifstream& stream, const std::vector<uint32_t>& frameOffsets, uint32_t nextGroupOffset, bool isLast) | |
{ | |
std::vector<Frame> frames; | |
for(size_t f = 0; f < frameOffsets.size(); f++) | |
//for(uint32_t frameOffset: frameOffsets) | |
{ | |
uint32_t startOffset = frameOffsets[f]; | |
bool isLast = f == frameOffsets.size() - 1; | |
uint32_t endOffset = isLast ? nextGroupOffset : frameOffsets[f + 1]; | |
//printf("\t\tendOffset = %d (0x%x)\n", endOffset, endOffset); | |
//printf("\t\tstartOffset = %d (0x%x)\n", startOffset, startOffset); | |
stream.seekg(startOffset, std::ios::beg); | |
uint32_t frameSize = endOffset - startOffset; | |
//printf("\t\tframeSize = %d (0x%x)\n", frameSize, frameSize); | |
//printf("\n\n"); | |
std::vector<uint8_t> image; | |
image.resize(frameSize); | |
stream.read(reinterpret_cast<char*>(image.data()), frameSize); | |
Frame frame(startOffset, image); | |
frames.push_back(frame); | |
} | |
return frames; | |
} | |
std::vector<Clip> findClips(std::ifstream& stream, const std::vector<uint32_t>& groupOffsets) | |
{ | |
std::vector<Clip> clips; | |
for(size_t i = 0; i < groupOffsets.size(); i++) | |
{ | |
uint32_t groupOffset = groupOffsets[i]; | |
stream.seekg(groupOffset, std::ios::beg); | |
Clip clip; | |
clip.offset = groupOffset; | |
clip.frameCount = readLittleEndianDoubleWord(stream); | |
for(uint32_t i = 0; i < clip.frameCount; i++) | |
{ | |
uint32_t frameOffset = readLittleEndianDoubleWord(stream) + groupOffset; | |
clip.frameOffsets.push_back(frameOffset); | |
} | |
clip.nextOffset = readLittleEndianDoubleWord(stream); | |
bool isLast = i == groupOffsets.size() - 1; | |
clip.frames = findFrames(stream, clip.frameOffsets, groupOffset + clip.nextOffset, isLast); | |
clips.push_back(clip); | |
} | |
return clips; | |
} | |
}; | |
void writeToFile(const CLX& clx, const std::string& path) | |
{ | |
std::ofstream stream(path, std::ios::binary); | |
for(const uint32_t offset: clx.groupOffsets) | |
{ | |
writeLittleEndianDoubleWord(stream, offset); | |
} | |
std::cout << "Wrote group offsets" << std::endl; | |
for(const Clip& clip: clx.clips) | |
{ | |
stream.seekp(clip.offset); | |
writeLittleEndianDoubleWord(stream, clip.frameCount); | |
for(uint32_t frameOffset: clip.frameOffsets) | |
{ | |
writeLittleEndianDoubleWord(stream, frameOffset - clip.offset); | |
} | |
writeLittleEndianDoubleWord(stream, clip.nextOffset); | |
} | |
std::cout << "Wrote clip headers" << std::endl; | |
for(const Clip& clip: clx.clips) | |
{ | |
for(const Frame& frame: clip.frames) | |
{ | |
stream.seekp(frame.offset); | |
stream.write(reinterpret_cast<const char*>(frame.image.data()), frame.image.size()); | |
} | |
} | |
std::cout << "Wrote frames" << std::endl; | |
} | |
void removeGroups(std::string clxPath, const std::vector<size_t>& groupIndexes) | |
{ | |
CLX clx(clxPath); | |
std::set<size_t, std::greater<size_t>> toRemove(groupIndexes.begin(), groupIndexes.end()); | |
std::cout << clx.groupOffsets.size() << " groups in file " << clxPath << std::endl; | |
for(size_t groupIndex: toRemove) | |
{ | |
std::cout << "Removing group #" << groupIndex << std::endl; | |
clx.removeGroup(groupIndex); | |
} | |
std::cout << clx.groupOffsets.size() << " groups" << std::endl; | |
writeToFile(clx, clxPath + ".stripped"); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
std::vector<size_t> toRemove { 1, 2, 3, 4, 5, 6, 7 }; | |
removeGroups(argv[1], toRemove); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment