/test/psd-test/psd-test.vcxproj.user
freetype
+tmp
+bin
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Debug|Win32.ActiveCfg = Debug|x64\r
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Debug|x64.ActiveCfg = Debug|x64\r
- {156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Debug|x64.Build.0 = Debug|x64\r
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Release|Mixed Platforms.Build.0 = Release|x64\r
{156C91E0-8CAC-4DBA-A212-AAFDAFACD8BC}.Release|Win32.ActiveCfg = Release|x64\r
* Author: Niklas P Andersson, niklas.p.andersson@svt.se
*/
+#include <memory>
#include "descriptor.h"
+#include <boost\property_tree\ptree.hpp>
#include "misc.h"
namespace caspar { namespace psd {
-bool descriptor::has_item(const std::wstring& key) const
-{
- return (items_.find(key) != items_.end());
-}
+ class descriptor::context::scoped_holder
+ {
+ descriptor::context::ptr_type ctx_;
+ public:
+ explicit scoped_holder(const std::wstring& key, descriptor::context::ptr_type ctx) : ctx_(ctx)
+ {
+ Ptree *parent = ctx_->stack.back();
+ Ptree *child = &parent->push_back(std::make_pair(key, Ptree()))->second;
+ ctx_->stack.push_back(child);
+ }
+ ~scoped_holder()
+ {
+ ctx_->stack.pop_back();
+ }
+ };
-const descriptor_item& descriptor::get_item(const std::wstring& key) const
-{
- return (*(items_.find(key))).second;
-}
+ descriptor::descriptor() : context_(std::make_shared<context>())
+ {
+ context_->stack.push_back(&context_->root);
+ }
+ descriptor::descriptor(const std::wstring& key, context::ptr_type context) : context_(context)
+ {
+ Ptree *parent = context_->stack.back();
+ Ptree *child = &parent->push_back(std::make_pair(key, Ptree()))->second;
+ context->stack.push_back(child);
+ }
+ descriptor::~descriptor()
+ {
+ context_->stack.pop_back();
+ }
bool descriptor::populate(BEFileInputStream& stream)
{
unsigned long element_count = stream.read_long();
for(int element_index = 0; element_index < element_count; ++element_index)
{
- descriptor_item item;
-
std::wstring key = stream.read_id_string();
- item.type = stream.read_long();
-
- switch(item.type)
- {
- case 'obj ': break;
- case 'Objc': break;
- case 'VlLs': break;
- case 'doub': break;
- case 'UntF': break;
-
- case 'TEXT':
- {
- item.text_text = stream.read_unicode_string();
- }
- break;
- case 'enum':
- {
- item.enum_key = stream.read_id_string();
- item.enum_val = stream.read_id_string();
- }
- break;
-
- case 'long':
- {
- item.long_value = stream.read_long();
- }
- break;
-
- case 'bool': break;
- case 'GlbO': break;
- case 'type':
- case 'GlbC':
- break;
- case 'alis': break;
-
- case 'tdta':
- {
- unsigned long rawdata_length = stream.read_long();
- item.rawdata_data.resize(rawdata_length);
- stream.read(item.rawdata_data.data(), rawdata_length);
- }
- break;
-
- default:
- //descriptor type not supported yet
- throw PSDFileFormatException();
- }
-
- items_.insert(std::pair<std::wstring, descriptor_item>(key, item));
+ read_value(key, stream);
}
}
catch(std::exception& ex)
return result;
}
+void descriptor::read_value(const std::wstring& key, BEFileInputStream& stream)
+{
+ unsigned int type = stream.read_long();
+
+ switch(type)
+ {
+ case 'Objc':
+ {
+ descriptor desc(key, context_);
+ desc.populate(stream);
+ }
+ break;
+
+ case 'doub':
+ {
+ context_->stack.back()->put(key, stream.read_double());
+ }
+ break;
+
+ case 'TEXT':
+ {
+ context_->stack.back()->put(key, stream.read_unicode_string());
+ }
+ break;
+
+ case 'enum':
+ {
+ context_->stack.back()->put(stream.read_id_string(), stream.read_id_string());
+ }
+ break;
+
+ case 'long':
+ {
+ context_->stack.back()->put(key, stream.read_long());
+ }
+ break;
+
+ case 'bool':
+ {
+ context_->stack.back()->put(key, stream.read_byte());
+ }
+ break;
+
+ case 'VlLs':
+ {
+ context::scoped_holder list(key, context_);
+ unsigned long count = stream.read_long();
+ for(int i = 0; i < count; ++i)
+ {
+ read_value(L"", stream);
+ }
+ }
+ break;
+
+ case 'tdta':
+ {
+ unsigned long rawdata_length = stream.read_long();
+ std::vector<char> rawdata(rawdata_length);
+ stream.read(rawdata.data(), rawdata_length);
+
+ std::wstring data_str(rawdata.begin(), rawdata.end());
+ context_->stack.back()->put(key, data_str);
+ }
+ break;
+
+ case 'obj ':
+ case 'UntF':
+ case 'GlbO':
+ case 'type':
+ case 'GlbC':
+ case 'alis':
+ default:
+ //descriptor type not supported yet
+ throw PSDFileFormatException();
+ }
+}
+
} //namespace psd
} //namespace caspar
\ No newline at end of file
#include <map>
#include <string>
#include <memory>
+#include <boost\property_tree\ptree.hpp>
#include "util\bigendian_file_input_stream.h"
namespace caspar { namespace psd {
- struct descriptor_item
- {
- unsigned long type;
+ //struct descriptor_item
+ //{
+ // unsigned long type;
- std::wstring enum_key;
- std::wstring enum_val;
+ // std::wstring enum_key;
+ // std::wstring enum_val;
- std::wstring text_text;
+ // std::wstring text_text;
- unsigned long long_value;
-
- std::vector<char> rawdata_data;
- };
+ // unsigned long long_value;
+ //
+ // std::vector<char> rawdata_data;
+ //};
class descriptor
{
- typedef std::map<std::wstring, descriptor_item> items_map;
+ typedef boost::property_tree::wptree Ptree;
+ struct context
+ {
+ typedef std::shared_ptr<context> ptr_type;
+
+ Ptree root;
+ std::vector<Ptree *> stack;
+
+ friend descriptor;
+ friend class scoped_holder;
+ class scoped_holder;
+ };
+ friend class context::scoped_holder;
+
+ descriptor(const descriptor&);
+ const descriptor& operator=(const descriptor&);
+ explicit descriptor(const std::wstring& key, context::ptr_type context);
public:
+ descriptor();
+ ~descriptor();
+
bool populate(BEFileInputStream& stream);
- const descriptor_item& get_item(const std::wstring& key) const;
- bool has_item(const std::wstring& key) const;
+ Ptree& items() const { return context_->root; }
private:
- items_map items_;
+ void read_value(const std::wstring& key, BEFileInputStream& stream);
+ context::ptr_type context_;
};
} //namespace psd
*/
#include "doc.h"
+#include "descriptor.h"
#include <iostream>
namespace caspar { namespace psd {
std::wstring name = input_.read_pascal_string(2);
unsigned long resource_length = input_.read_long();
+ unsigned long end_of_chunk = input_.current_position() + resource_length;
- //TODO: read actual data
- switch(resource_id)
+ try
{
- case 1026: //layer group information. describes linked layers
+ //TODO: read actual data
+ switch(resource_id)
{
- int layer_count = resource_length / 2;
- for(int i = 0; i < layer_count; ++i)
+ case 1026: //layer group information. describes linked layers
{
- short id = input_.read_short();
+ int layer_count = resource_length / 2;
+ for(int i = 0; i < layer_count; ++i)
+ {
+ short id = input_.read_short();
- if(i == layers_.size())
- layers_.push_back(std::make_shared<layer>());
+ if(i == layers_.size())
+ layers_.push_back(std::make_shared<layer>());
- layers_[i]->set_link_group_id(id);
- }
+ layers_[i]->set_link_group_id(id);
+ }
- }
- break;
-
- case 1005:
- {
- ////resolutionInfo
- //struct ResolutionInfo
- //{
- //Fixed hRes;
- //int16 hResUnit;
- //int16 widthUnit;
- //Fixed vRes;
- //int16 vResUnit;
- //int16 heightUnit;
- //};
- }
- case 1006: //names of alpha channels
- case 1008: //caption
- case 1010: //background color
- case 1024: //layer state info (2 bytes containing the index of target layer (0 = bottom layer))
- case 1028: //IPTC-NAA. File Info...
- case 1029: //image for raw format files
- case 1036: //thumbnail resource
- case 1045: //unicode Alpha names (Unicode string (4 bytes length followed by string))
- case 1053: //alpha identifiers (4 bytes of length, followed by 4 bytes each for every alpha identifier.)
- case 1060: //XMP metadata
- case 1065: //layer comps
- case 1069: //layer selection ID(s)
- case 1072: //layer group(s) enabled id
- case 1075: //timeline information
- case 1077: //DisplayInfo
- case 2999: //name of clipping path
- default:
- {
- if(resource_id >= 2000 && resource_id <=2997) //path information
+ }
+ break;
+ case 1075: //timeline information
{
+ int desc_ver = input_.read_long(); //descriptor version, should be 16
+ descriptor timeline_descriptor;
+ if(!timeline_descriptor.populate(input_))
+ throw PSDFileFormatException();
+ else
+ {
+ timeline_desc_.swap(timeline_descriptor.items());
+ }
}
- else if(resource_id >= 4000 && resource_id <= 4999) //plug-in resources
+ break;
+
+ case 1005:
+ {
+ ////resolutionInfo
+ //struct ResolutionInfo
+ //{
+ //Fixed hRes;
+ //int16 hResUnit;
+ //int16 widthUnit;
+ //Fixed vRes;
+ //int16 vResUnit;
+ //int16 heightUnit;
+ //};
+ }
+ case 1006: //names of alpha channels
+ case 1008: //caption
+ case 1010: //background color
+ case 1024: //layer state info (2 bytes containing the index of target layer (0 = bottom layer))
+ case 1028: //IPTC-NAA. File Info...
+ case 1029: //image for raw format files
+ case 1036: //thumbnail resource
+ case 1045: //unicode Alpha names (Unicode string (4 bytes length followed by string))
+ case 1053: //alpha identifiers (4 bytes of length, followed by 4 bytes each for every alpha identifier.)
+ case 1060: //XMP metadata
+ case 1065: //layer comps
+ case 1069: //layer selection ID(s)
+ case 1072: //layer group(s) enabled id
+ case 1077: //DisplayInfo
+ case 2999: //name of clipping path
+ default:
{
+ if(resource_id >= 2000 && resource_id <=2997) //path information
+ {
+ }
+ else if(resource_id >= 4000 && resource_id <= 4999) //plug-in resources
+ {
+ }
}
+ input_.discard_bytes(resource_length);
+ break;
}
- input_.discard_bytes(resource_length);
- break;
- }
- if(resource_length & 1 == 1) //each resource is padded to an even amount of bytes
- input_.discard_bytes(1);
+ if(resource_length & 1 == 1) //each resource is padded to an even amount of bytes
+ input_.discard_bytes(1);
+ }
+ catch(PSDFileFormatException&)
+ {
+ input_.set_position(end_of_chunk);
+ }
}
}
catch(PSDFileFormatException& ex)
{
return filename_;
}
+ bool has_timeline() const
+ {
+ return !timeline_desc_.empty();
+ }
+ const boost::property_tree::wptree& timeline() const
+ {
+ return timeline_desc_;
+ }
bool parse(const std::wstring& s);
void read_image_resources();
void read_layers();
- std::wstring filename_;
- BEFileInputStream input_;
+ std::wstring filename_;
+ BEFileInputStream input_;
- std::vector<layer_ptr> layers_;
+ std::vector<layer_ptr> layers_;
- unsigned short channels_;
- unsigned long width_;
- unsigned long height_;
- unsigned short depth_;
- psd::color_mode color_mode_;
+ unsigned short channels_;
+ unsigned long width_;
+ unsigned long height_;
+ unsigned short depth_;
+ psd::color_mode color_mode_;
+ boost::property_tree::wptree timeline_desc_;
};
} //namespace psd
name_ = stream.read_pascal_string(4);
//Aditional Layer Information
- unsigned long end_of_layer_info = position1 + extraDataSize;
+ long end_of_layer_info = position1 + extraDataSize;
try
{
while(stream.current_position() < end_of_layer_info)
{
- unsigned long signature = stream.read_long();
- if(signature != '8BIM' && signature != '8B64')
- throw PSDFileFormatException();
+ read_chunk(stream);
+ }
+ }
+ catch(PSDFileFormatException&)
+ {
+ stream.set_position(end_of_layer_info);
+ }
+}
- unsigned long key = stream.read_long();
- unsigned long length = stream.read_long();
- unsigned long end_of_chunk = stream.current_position() + length;
+void layer::read_chunk(BEFileInputStream& stream, bool isMetadata)
+{
+ unsigned long signature = stream.read_long();
+ if(signature != '8BIM' && signature != '8B64')
+ throw PSDFileFormatException();
- if(key == 'lspf')
- {
- protection_flags_ = stream.read_long();
- }
- else if(key == 'TySh') //type tool object settings
- {
- std::wstring text; //the text in the layer
+ unsigned long key = stream.read_long();
+
+ if(isMetadata) stream.read_long();
- stream.read_short(); //should be 1
- stream.discard_bytes(6*8); //just throw transformation info for now
- stream.read_short(); //"text version" should be 50
- stream.read_long(); //"descriptor version" should be 16
+ unsigned long length = stream.read_long();
+ unsigned long end_of_chunk = stream.current_position() + length;
- //text data descriptor ('descriptor structure')
- descriptor text_descriptor;
- if(!text_descriptor.populate(stream))
- throw PSDFileFormatException();
- else
- {
- if(text_descriptor.has_item(L"EngineData"))
- {
- const descriptor_item& text_data = text_descriptor.get_item(L"EngineData");
- read_pdf(text_layer_info_, text_data.rawdata_data);
- }
- }
-
- stream.read_short(); //"warp version" should be 1
- stream.read_long(); //"descriptor version" should be 16
-
- //warp data descriptor ('descriptor structure')
- descriptor warp_descriptor;
- if(!text_descriptor.populate(stream))
- throw PSDFileFormatException();
+ try
+ {
+ switch(key)
+ {
+ case 'lspf': //protection settings
+ protection_flags_ = stream.read_long();
+ break;
+
+ case 'Txt2': //text engine data
+ break;
+
+ case 'TySh': //type tool object settings
+ read_text_data(stream);
+ break;
+
+ case 'shmd': //metadata
+ read_metadata(stream);
+ break;
+
+ case 'lyvr': //layer version
+ break;
+
+ case 'lnkD': //linked layer
+ case 'lnk2': //linked layer
+ case 'lnk3': //linked layer
+ break;
+
+ case 'tmln':
+ read_timeline_data(stream);
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch(PSDFileFormatException&)
+ {
+ //ignore failed chunks silently
+ }
- stream.discard_bytes(4*8); //top, left, right, bottom
- }
+ stream.set_position(end_of_chunk);
+}
- stream.set_position(end_of_chunk);
- }
+void layer::read_metadata(BEFileInputStream& stream)
+{
+ unsigned long count = stream.read_long();
+ for(int index = 0; index < count; ++index)
+ read_chunk(stream, true);
+}
+
+void layer::read_timeline_data(BEFileInputStream& stream)
+{
+ stream.read_long(); //"descriptor version" should be 16
+ descriptor timeline_descriptor;
+ if(!timeline_descriptor.populate(stream))
+ throw PSDFileFormatException();
+ else
+ {
+ timeline_info_.swap(timeline_descriptor.items());
}
- catch(PSDFileFormatException& ex)
+}
+
+void layer::read_text_data(BEFileInputStream& stream)
+{
+ std::wstring text; //the text in the layer
+
+ stream.read_short(); //should be 1
+ stream.discard_bytes(6*8); //just throw transformation info for now
+ stream.read_short(); //"text version" should be 50
+
+ //text data descriptor ('descriptor structure')
+ if(stream.read_long() != 16) //"descriptor version" should be 16
+ throw PSDFileFormatException();
+
+ descriptor text_descriptor;
+ if(!text_descriptor.populate(stream))
+ throw PSDFileFormatException();
+ else
{
- stream.set_position(end_of_layer_info);
+ auto text_info = text_descriptor.items().get_optional<std::wstring>(L"EngineData");
+ if(text_info.is_initialized())
+ {
+ std::string str(text_info.get().begin(), text_info.get().end());
+ read_pdf(text_layer_info_, str);
+ }
}
+
+ stream.read_short(); //"warp version" should be 1
+
+ //warp data descriptor ('descriptor structure')
+ stream.read_long(); //"descriptor version" should be 16
+ descriptor warp_descriptor;
+ if(!warp_descriptor.populate(stream))
+ throw PSDFileFormatException();
+ else
+ stream.discard_bytes(4*8); //top, left, right, bottom
}
void layer::layer_mask::read_mask_data(BEFileInputStream& stream)
{
return !text_layer_info_.empty();
}
+bool layer::has_timeline() const
+{
+ return !timeline_info_.empty();
+}
+
//TODO: implement
void layer::read_blending_ranges(BEFileInputStream& stream)
friend class layer;
public:
- bool enabled() { return (flags_ & 2) == 0; }
- bool linked() { return (flags_ & 1) == 0; }
- bool inverted() { return (flags_ & 4) == 4; }
+ bool enabled() const { return (flags_ & 2) == 0; }
+ bool linked() const { return (flags_ & 1) == 0; }
+ bool inverted() const { return (flags_ & 4) == 4; }
void read_mask_data(BEFileInputStream&);
bool visible() { return (flags_ & 2) == 0; } //the (PSD file-format) documentation is is saying the opposite but what the heck
bool is_position_protected() { return (protection_flags_& 4) == 4; }
+ int protection_flags() const { return protection_flags_; }
bool is_text() const;
+ bool has_timeline() const;
const boost::property_tree::wptree& text_data() const { return text_layer_info_; }
+ const boost::property_tree::wptree& timeline_data() const { return timeline_info_; }
const image8bit_ptr& image() const { return image_; }
private:
channel_ptr get_channel(channel_type);
+ void read_chunk(BEFileInputStream& stream, bool isMetadata = false);
void read_blending_ranges(BEFileInputStream&);
+ void read_text_data(BEFileInputStream&);
+ void read_metadata(BEFileInputStream& stream);
+ void read_timeline_data(BEFileInputStream&);
caspar::psd::rect<long> rect_;
std::vector<channel_ptr> channels_;
image8bit_ptr image_;
boost::property_tree::wptree text_layer_info_;
+ boost::property_tree::wptree timeline_info_;
};
} //namespace psd
return SWAP32(in);
}
+double BEFileInputStream::read_double()
+{
+ char data[8];
+ for(int i = 0; i < 8; ++i)
+ data[7-i] = read_byte();
+
+ return *reinterpret_cast<double*>(data);
+}
void BEFileInputStream::read(char* buf, unsigned long length)
{
std::wstring read_pascal_string(unsigned char padding = 1);
std::wstring read_unicode_string();
std::wstring read_id_string();
+ double read_double();
void discard_bytes(unsigned long);
void discard_to_next_word();
};
};
-bool read_pdf(boost::property_tree::wptree& tree, const std::vector<char>& v)
+bool read_pdf(boost::property_tree::wptree& tree, const std::string& s)
{
- typedef std::vector<char>::const_iterator iterator_type;
+ typedef std::string::const_iterator iterator_type;
pdf_grammar<iterator_type> g;
bool result = false;
try
{
- auto it = v.begin();
- result = qi::phrase_parse(it, v.end(), g, qi::space);
+ auto it = s.begin();
+ result = qi::phrase_parse(it, s.end(), g, qi::space);
if(result)
tree.swap(g.context.root);
}
#pragma once
#include <boost/property_tree/ptree.hpp>
-#include <vector>
+#include <string>
namespace caspar { namespace psd {
-bool read_pdf(boost::property_tree::wptree& tree, const std::vector<char>& v);
+bool read_pdf(boost::property_tree::wptree& tree, const std::string& s);
} //namespace psd
} //namespace caspar
\ No newline at end of file
int _tmain(int argc, _TCHAR* argv[])
{
- caspar::psd::psd_document doc;
- doc.parse(L"C:\\Lokala Filer\\Utveckling\\CasparCG\\Server 2.1\\shell\\media\\test1.psd");
-
- std::wstringstream trace;
-
- trace << L"<doc filename='" << doc.filename() << L"' color_mode='" << caspar::psd::color_mode_to_string(doc.color_mode()) << L"' color_depth='" << doc.color_depth() << L"' channel_count='" << doc.channels_count() << L"' width='" << doc.width() << L"' height='" << doc.height() << L"'>" << std::endl;
-
- auto end = doc.layers().end();
- for(auto it = doc.layers().begin(); it != end; ++it)
+ if(argc > 1)
{
- caspar::psd::layer_ptr layer = (*it);
- trace << L" <layer name='" << layer->name() << L"' opacity='" << layer->opacity() << L"' flags='" << std::hex << layer->flags() << std::dec << "'>" << std::endl;
- if(layer->image())
- trace << L" <bounding-box left='" << layer->rect().left << "' top='" << layer->rect().top << "' right='" << layer->rect().right << "' bottom='" << layer->rect().bottom << "' />" << std::endl;
- if(layer->mask())
- trace << L" <mask default-value='" << layer->mask_info().default_value_ << "' flags='" << std::hex << (unsigned short) layer->mask_info().flags_ << std::dec << "' left='" << layer->mask_info().rect_.left << "' top='" << layer->mask_info().rect_.top << "' right='" << layer->mask_info().rect_.right << "' bottom='" << layer->mask_info().rect_.bottom << "' />" << std::endl;
- if(layer->is_text())
+ caspar::psd::psd_document doc;
+ if(doc.parse(argv[1]))
{
- boost::property_tree::xml_writer_settings<wchar_t> w(' ', 3);
- boost::property_tree::write_xml(trace, (*it)->text_data(), w);
- }
- trace << L" </layer>" << std::endl;
- }
+ std::wstringstream trace;
- trace << L"</doc>" << std::endl;
+ trace << L"<doc filename='" << doc.filename() << L"' color_mode='" << caspar::psd::color_mode_to_string(doc.color_mode()) << L"' color_depth='" << doc.color_depth() << L"' channel_count='" << doc.channels_count() << L"' width='" << doc.width() << L"' height='" << doc.height() << L"'>" << std::endl;
- std::ofstream log("psd-log.txt");
- log << caspar::u8(trace.str());
- std::cout << caspar::u8(trace.str());
+ auto end = doc.layers().end();
+ for(auto it = doc.layers().begin(); it != end; ++it)
+ {
+ caspar::psd::layer_ptr layer = (*it);
+ trace << L" <layer name='" << layer->name() << L"' opacity='" << layer->opacity() << L"' visible='" << layer->visible() << L"' protection-flags='" << std::hex << layer->protection_flags() << std::dec << L"'>" << std::endl;
+ //if(layer->image())
+ // trace << L" <bounding-box left='" << layer->rect().left << L"' top='" << layer->rect().top << L"' right='" << layer->rect().right << L"' bottom='" << layer->rect().bottom << L"' />" << std::endl;
+ //if(layer->mask())
+ // trace << L" <mask default-value='" << layer->mask_info().default_value_ << L"' enabled=" << layer->mask_info().enabled() << L" linked=" << layer->mask_info().linked() << L" inverted=" << layer->mask_info().inverted() << L" left='" << layer->mask_info().rect_.left << L"' top='" << layer->mask_info().rect_.top << "' right='" << layer->mask_info().rect_.right << "' bottom='" << layer->mask_info().rect_.bottom << "' />" << std::endl;
+ if(layer->is_text())
+ {
+ trace << L" <text value='" << (*it)->text_data().get(L"EngineDict.Editor.Text", L"") << L"' />" << std::endl;
+ //boost::property_tree::xml_writer_settings<wchar_t> w(' ', 3);
+ //boost::property_tree::write_xml(trace, (*it)->text_data(), w);
+ }
+ if(layer->has_timeline())
+ {
+ trace << L" <timeline>" << std::endl;
+ boost::property_tree::xml_writer_settings<wchar_t> w(' ', 3);
+ boost::property_tree::write_xml(trace, (*it)->timeline_data(), w);
+ trace << L" </timeline>" << std::endl;
+ }
+ trace << L" </layer>" << std::endl;
+ }
+
+ trace << L"</doc>" << std::endl;
+ std::ofstream log("psd-log.txt");
+ log << caspar::u8(trace.str());
+ std::cout << caspar::u8(trace.str());
+ }
+ }
return 0;
}