Aegisub/prs/prs_file.cpp

463 lines
11 KiB
C++

// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:zeratul@cellosoft.com
//
///////////
// Headers
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <fstream>
#include "prs_file.h"
#include "prs_entry.h"
#include "prs_image.h"
#include "prs_display.h"
///////////////
// Constructor
PRSFile::PRSFile () {
// Cache data
cacheMemSize = 0;
maxCache = 8 << 20;
}
//////////////
// Destructor
PRSFile::~PRSFile() {
Reset();
ClearCache();
}
//////////////
// Reset file
void PRSFile::Reset() {
// Clear list of entries
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
delete *cur;
}
entryList.clear();
}
///////////////////
// Clear the cache
void PRSFile::ClearCache() {
// Clear list of cached frames
frameCache.clear();
// Zero size
cacheMemSize = 0;
}
////////
// Save
void PRSFile::Save(std::string path) {
// I'm using C's stdio instead of C++'s fstream because I feel that fstream is
// pretty lame for binary data, so I need to make sure that no exceptions are thrown from here.
// TODO: Make this endianness-independent
// Open file
FILE *fp = fopen(path.c_str(),"wb");
if (!fp) throw std::exception("Failed to open file");
try {
// Write the "PRS" (zero-terminated) string ID (4 bytes)
fwrite("PRS-BIN",1,8,fp);
// Write version number (4 bytes)
unsigned __int32 temp = 1;
fwrite(&temp,4,1,fp);
// Write stream name (for future scalability, there is only one for now)
// This is writen as 4 bytes for length, and then length bytes for actual name.
// Since we set length to zero, the actual name string is never writen.
temp = 0;
fwrite(&temp,4,1,fp);
// Write data blocks
std::vector<char> vec;
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
// Data blocks take care of writing themselves
// All of them start with a 4-byte string identifier, and a 4-byte length identifier
// A decoder can (and should!) ignore any block that it doesn't recognize
vec.resize(0);
(*cur)->WriteData(vec);
fwrite(&vec[0],1,vec.size(),fp);
}
}
// Rethrow exceptions
catch (...) {
fclose(fp);
throw;
}
// Close file
fclose(fp);
}
////////
// Load
void PRSFile::Load(std::string path, bool reset) {
// Reset first, if requested
if (reset) Reset();
// Open file
FILE *fp = fopen(path.c_str(),"rb");
if (!fp) throw std::exception("Failed to open file");
try {
// Read first eight bytes
char buf[16];
fread(buf,1,8,fp);
if (memcmp(buf,"PRS-BIN",8) != 0) {
// Is actually ASCII, read as that
if (memcmp(buf,"PRS-ASC",7) == 0) {
LoadText(path,false);
return;
}
// Invalid
throw std::exception("Invalid file type.");
}
// Read version number
unsigned __int32 temp = 0;
fread(&temp,4,1,fp);
if (temp != 1) throw std::exception("Invalid version.");
// Read stream name length
fread(&temp,4,1,fp);
// Read stream name
if (temp > 0) {
char *streamName = new char[temp+1];
fread(streamName,1,temp,fp);
// We don't need it, so delete afterwards
delete streamName;
}
// Temporary vector
std::vector<char> vec;
// Read data blocks
while (!feof(fp)) {
// Read identifier and size
fread(buf,1,4,fp);
fread(&temp,4,1,fp);
// Image block
if (strcmp(buf,"IMG") == 0) {
// Read data
vec.resize(temp);
fread(&vec[0],1,temp,fp);
// Create object
PRSImage *img = new PRSImage;
img->ReadData(vec);
AddEntry(img);
}
// Display block
else if (strcmp(buf,"DSP") == 0) {
// Read data
vec.resize(temp);
fread(&vec[0],1,temp,fp);
// Create object
PRSDisplay *disp = new PRSDisplay;
disp->ReadData(vec);
AddEntry(disp);
}
// Unknown block, ignore it
else {
fseek(fp,temp,SEEK_CUR);
}
}
}
// Rethrow exceptions
catch (...) {
fclose(fp);
throw;
}
// Close file
fclose(fp);
}
/////////////
// Add entry
void PRSFile::AddEntry(PRSEntry *entry) {
entryList.push_back(entry);
}
//////////////////////////////////////////////////
// Checks if there is any duplicate of this image
PRSImage *PRSFile::FindDuplicateImage(PRSImage *img) {
// Scan looking for duplicate hashes
PRSImage *orig;
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
orig = PRSEntry::GetImage(*cur);
if (orig) {
// Compare data lengths
if (orig->dataLen == img->dataLen) {
// Identical data lengths, compare hashes
if (memcmp(orig->md5,img->md5,16) == 0) {
// Identical hashes, compare image data to be sure
if (memcmp(orig->data,img->data,orig->dataLen) == 0) {
// Identical data, return
return orig;
}
}
}
}
}
// No duplicate found
return NULL;
}
////////////////
// Draw a frame
void PRSFile::DrawFrame(int n,PRSVideoFrame *frame) {
// Get list of display blocks
std::vector<PRSDisplay*> blocks;
GetDisplayBlocksAtFrame(n,blocks);
// Draw the blocks
int nblocks = (int) blocks.size();
for (int i=0;i<nblocks;i++) {
// Get display block and frame
PRSDisplay *display = blocks[i];
PRSVideoFrame *overFrame = CachedGetFrameByID(display->id);
// Draw image on frame
if (overFrame) overFrame->Overlay(frame,display->x,display->y,display->alpha,display->blend);
// DON'T delete the frame!
// The cache takes care of doing so.
}
}
///////////////////////////////////////////////////////////////////
// Gets a frame from cache, or load it there if it's not available
PRSVideoFrame* PRSFile::CachedGetFrameByID(int id) {
// Check if the image is already decoded on cache, fetch it if it is
PRSVideoFrame *frame = NULL;
std::list<PRSCachedFrame>::iterator cur;
for (cur=frameCache.begin();cur!=frameCache.end();cur++) {
if ((*cur).id == id) {
return (*cur).frame;
}
}
// It isn't; decode and add it to cache
// Get image
PRSImage *image = GetImageByID(id);
if (!image) return NULL;
// Get frame
frame = image->GetDecodedFrame();
// Add to cache
if (frame) {
// Add and raise size
PRSCachedFrame cached;
cached.frame = frame;
cached.id = id;
frameCache.push_front(cached);
cached.frame = NULL;
cacheMemSize += frame->GetSize();
// Update cache
EnforceCacheLimit();
}
// Return it
return frame;
}
/////////////////////////////////////////////////////////////
// Enforce the cache limit, that is, delete anything over it
// This function will always leave at least one image on cache
void PRSFile::EnforceCacheLimit() {
while (cacheMemSize > maxCache && frameCache.size() > 1) {
cacheMemSize -= frameCache.back().frame->GetSize();
frameCache.pop_back();
}
}
////////////////////////////
// Set maximum cache memory
void PRSFile::SetCacheLimit(int bytes) {
maxCache = bytes;
EnforceCacheLimit();
}
////////////////////////////////////////////////
// Finds which display blocks are at a position
void PRSFile::GetDisplayBlocksAtFrame(int n,std::vector<PRSDisplay*> &blocks) {
// Find all blocks that match
unsigned int fn = n;
std::list<PRSEntry*>::iterator cur;
PRSDisplay *display;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
display = PRSEntry::GetDisplay(*cur);
if (display) {
if (display->startFrame <= fn && display->endFrame >= fn) {
blocks.push_back(display);
}
}
}
// Sort them by layer
// TODO
}
////////////////////////////////////////////
// Checks if there is anything at the frame
bool PRSFile::HasDataAtFrame(int n) {
// Find all blocks that match
unsigned int fn = n;
std::list<PRSEntry*>::iterator cur;
PRSDisplay *display;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
display = PRSEntry::GetDisplay(*cur);
if (display) {
if (display->startFrame <= fn && display->endFrame >= fn) {
return true;
}
}
}
return false;
}
///////////////////////////////////////////////////////////////
// Gets a PRSImage by its ID, returns NULL if it doesn't exist
PRSImage *PRSFile::GetImageByID(int id) {
// Search for image
std::list<PRSEntry*>::iterator cur;
PRSImage *img;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
img = PRSEntry::GetImage(*cur);
if (img && img->id == id) return img;
}
// Not found
return NULL;
}
//////////////////////
// Save as plain-text
void PRSFile::SaveText(std::string path) {
// Open file
std::ofstream file;
file.open(path.c_str(),std::fstream::out);
// Write version string
file << "PRS-ASCII v1" << std::endl;
// Write events
std::list<PRSEntry*>::iterator cur;
for (cur=entryList.begin();cur!=entryList.end();cur++) {
PRSImage *img;
PRSDisplay *display;
// Is image?
img = PRSEntry::GetImage(*cur);
if (img) {
// Get image filename
char idStr[64];
itoa(img->id,idStr,10);
std::string imgfile = path;
imgfile += ".id.";
imgfile += idStr;
imgfile += ".png";
// Copy to disk
FILE *fp = fopen(imgfile.c_str(),"wb");
if (fp) {
fwrite(img->data,1,img->dataLen,fp);
fclose(fp);
}
// Write text
file << "IMG: " << img->id << "," << img->imageType << "," << imgfile.c_str() << std::endl;
continue;
}
// Is display?
display = PRSEntry::GetDisplay(*cur);
if (display) {
// Write text
file << "DSP: " << display->startFrame << "," << display->endFrame << "," << display->start << "," << display->end << ",";
file << display->id << "," << display->x << "," << display->y << "," << (int)(display->alpha) << "," << (int)(display->blend);
file << std::endl;
continue;
}
}
// Close file
file.close();
}
///////////////////
// Load plain-text
void PRSFile::LoadText(std::string path, bool reset) {
}