mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 06:56:04 +02:00
feat: Add metadata removal functionality
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -117,6 +118,35 @@ public struct PNGChunk
|
|||||||
return new Tuple<int, int>(width, height);
|
return new Tuple<int, int>(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates this chunk against the chunk at the same index in a given file stream, checking the chunk length and CRC.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileStream">The file stream from which the chunk data is read for validation.</param>
|
||||||
|
/// <returns>True if chunk exists at index and is valid, false otherwise.</returns>
|
||||||
|
public bool ExistsInFile(FileStream fileStream)
|
||||||
|
{
|
||||||
|
fileStream.Seek(Index, SeekOrigin.Begin);
|
||||||
|
byte[] buffer = new byte[Length];
|
||||||
|
fileStream.ReadExactly(buffer, 0, Length);
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
Array.Reverse(buffer, 0, 4);
|
||||||
|
|
||||||
|
int chunkLength = BitConverter.ToInt32(buffer, 0);
|
||||||
|
if (chunkLength != Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fileStream.Seek(4 + chunkLength, SeekOrigin.Current);
|
||||||
|
fileStream.ReadExactly(buffer, 0, Length);
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
Array.Reverse(buffer, 0, 4);
|
||||||
|
|
||||||
|
uint crc = BitConverter.ToUInt32(buffer, 0);
|
||||||
|
|
||||||
|
return crc == CalculateCRC();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs and returns a byte array representation of the PNG chunk. Generates a CRC.
|
/// Constructs and returns a byte array representation of the PNG chunk. Generates a CRC.
|
||||||
/// This data can be added to a PNG file as-is.
|
/// This data can be added to a PNG file as-is.
|
||||||
@@ -146,7 +176,7 @@ public struct PNGChunk
|
|||||||
Buffer.BlockCopy(Data, 0, result, 8, Data.Length);
|
Buffer.BlockCopy(Data, 0, result, 8, Data.Length);
|
||||||
|
|
||||||
// Calculate and copy CRC
|
// Calculate and copy CRC
|
||||||
uint crc = Crc32(Data, 0, Data.Length, Crc32(chunkTypeBytes, 0, chunkTypeBytes.Length, 0));
|
uint crc = CalculateCRC();
|
||||||
uint reversedCrc = BinaryPrimitives.ReverseEndianness(crc);
|
uint reversedCrc = BinaryPrimitives.ReverseEndianness(crc);
|
||||||
|
|
||||||
Buffer.BlockCopy(BitConverter.GetBytes(reversedCrc), 0, result, totalLength - 4, 4);
|
Buffer.BlockCopy(BitConverter.GetBytes(reversedCrc), 0, result, totalLength - 4, 4);
|
||||||
@@ -154,6 +184,12 @@ public struct PNGChunk
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint CalculateCRC()
|
||||||
|
{
|
||||||
|
var chunkTypeBytes = Encoding.ASCII.GetBytes(ChunkType);
|
||||||
|
return Crc32(Data, 0, Data.Length, Crc32(chunkTypeBytes, 0, chunkTypeBytes.Length, 0));
|
||||||
|
}
|
||||||
|
|
||||||
// Crc32 implementation from
|
// Crc32 implementation from
|
||||||
// https://web.archive.org/web/20150825201508/http://upokecenter.dreamhosters.com/articles/png-image-encoder-in-c/
|
// https://web.archive.org/web/20150825201508/http://upokecenter.dreamhosters.com/articles/png-image-encoder-in-c/
|
||||||
private static uint Crc32(byte[] stream, int offset, int length, uint crc)
|
private static uint Crc32(byte[] stream, int offset, int length, uint crc)
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ public class PNGFile : IDisposable
|
|||||||
fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096);
|
fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, 4096);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PNGFile(string filePath, int bufferSize)
|
||||||
|
{
|
||||||
|
fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the first PNG chunk of the specified type from the file, or null if none were found.
|
/// Retrieves the first PNG chunk of the specified type from the file, or null if none were found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -107,6 +112,56 @@ public class PNGFile : IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a PNG chunk from the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk">The PNG chunk to delete. Needs a valid index set.</param>
|
||||||
|
/// <returns>True if the chunk was successfully deleted, otherwise false.</returns>
|
||||||
|
public bool DeleteChunk(PNGChunk chunk)
|
||||||
|
{
|
||||||
|
if (!chunk.ExistsInFile(fileStream))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int bufferSize = 128 * 1024;
|
||||||
|
int deleteStart = chunk.Index;
|
||||||
|
int deleteLength = chunk.Length + CHUNK_NONDATA_SIZE;
|
||||||
|
|
||||||
|
long sourcePos = deleteStart + deleteLength;
|
||||||
|
long destPos = deleteStart;
|
||||||
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
// Copy everything after the deleted section forward
|
||||||
|
while (sourcePos < fileStream.Length)
|
||||||
|
{
|
||||||
|
fileStream.Seek(sourcePos, SeekOrigin.Begin);
|
||||||
|
int bytesRead = fileStream.Read(buffer, 0, Math.Min(buffer.Length, (int)(fileStream.Length - sourcePos)));
|
||||||
|
|
||||||
|
if (bytesRead == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
fileStream.Seek(destPos, SeekOrigin.Begin);
|
||||||
|
fileStream.Write(buffer, 0, bytesRead);
|
||||||
|
|
||||||
|
sourcePos += bytesRead;
|
||||||
|
destPos += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStream.SetLength(fileStream.Length - deleteLength);
|
||||||
|
|
||||||
|
metadataChunkCache.Remove(chunk);
|
||||||
|
|
||||||
|
// update the index of all cached chunks
|
||||||
|
for (int i = 0; i < metadataChunkCache.Count; i++)
|
||||||
|
{
|
||||||
|
var cachedChunk = metadataChunkCache[i];
|
||||||
|
if (cachedChunk.Index > deleteStart)
|
||||||
|
cachedChunk.Index -= deleteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all PNG metadata chunks of a specified type
|
/// Retrieves all PNG metadata chunks of a specified type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -66,6 +66,22 @@ namespace VRCX
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool DeleteTextChunk(string keyword, PNGFile pngFile)
|
||||||
|
{
|
||||||
|
var iTXtChunk = pngFile.GetChunksOfType(PNGChunkTypeFilter.iTXt);
|
||||||
|
if (iTXtChunk.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < iTXtChunk.Count; i++)
|
||||||
|
{
|
||||||
|
var data = iTXtChunk[i].ReadITXtChunk();
|
||||||
|
if (data.Item1 == keyword)
|
||||||
|
return pngFile.DeleteChunk(iTXtChunk[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a PNG text chunk ready for writing.
|
/// Generates a PNG text chunk ready for writing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -220,6 +220,15 @@ namespace VRCX
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void DeleteTextMetadata(string path, bool deleteVRChatMetadata = false)
|
||||||
|
{
|
||||||
|
using var pngFile = new PNGFile(path, 128 * 1024);
|
||||||
|
if (deleteVRChatMetadata)
|
||||||
|
PNGHelper.DeleteTextChunk("XML:com.adobe.xmp", pngFile);
|
||||||
|
|
||||||
|
PNGHelper.DeleteTextChunk("Description", pngFile);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool WriteVRCXMetadata(string text, string path)
|
public static bool WriteVRCXMetadata(string text, string path)
|
||||||
{
|
{
|
||||||
using var pngFile = new PNGFile(path);
|
using var pngFile = new PNGFile(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user