forked from mia/Aegisub
454297253c
Originally committed to SVN as r297.
462 lines
11 KiB
C++
462 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) {
|
|
}
|