package org.korsakow.services.export;
import java.awt.Dimension;
import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.log4j.Logger;
import org.korsakow.domain.Image;
import org.korsakow.domain.Media;
import org.korsakow.domain.Settings;
import org.korsakow.domain.interf.IImage;
import org.korsakow.domain.interf.IInterface;
import org.korsakow.domain.interf.IMedia;
import org.korsakow.domain.interf.IProject;
import org.korsakow.domain.interf.ISettings;
import org.korsakow.domain.interf.ISnu;
import org.korsakow.domain.interf.ISound;
import org.korsakow.domain.interf.IText;
import org.korsakow.domain.interf.IVideo;
import org.korsakow.domain.interf.IWidget;
import org.korsakow.domain.task.ITask;
import org.korsakow.ide.DataRegistry;
import org.korsakow.ide.lang.LanguageBundle;
import org.korsakow.ide.resources.WidgetType;
import org.korsakow.ide.task.DelegateTask;
import org.korsakow.ide.task.TaskException;
import org.korsakow.ide.util.FileUtil;
import org.korsakow.ide.util.Util;
import org.korsakow.services.encoders.font.FontFormat;
import org.korsakow.services.encoders.image.ImageFormat;
import org.korsakow.services.encoders.sound.SoundFormat;
import org.korsakow.services.encoders.video.VideoCodec;
import org.korsakow.services.export.task.CopyPlayerExportTask;
import org.korsakow.services.export.task.CreateFilenameMapTask;
import org.korsakow.services.export.task.FontExportTask;
import org.korsakow.services.export.task.ImageExportTask;
import org.korsakow.services.export.task.SoundExportTask;
import org.korsakow.services.export.task.SubtitleExportTask;
import org.korsakow.services.export.task.TextExportTask;
import org.korsakow.services.export.task.ThumbnailExportTask;
import org.korsakow.services.export.task.VideoExportTask;
import org.korsakow.services.export.task.XMLExportTask;
public class Exporter
{
public static final String DATA_DIR = "data";
public static final String FLASH_PLAYER_ROOT = "players/flash/";
public static final String FLASH_PLAYER_RESOURCE_INDEX = "index.html";
public static final String FLASH_PLAYER_RESOURCE_CSS = "data/css/style.css";
public static final String[] FLASH_PLAYER_RESOURES = {
"data/KorsakowPlayer.swf",
"data/Embed.swf",
"data/DebugWindow.swf",
"data/swf/expressInstall.swf",
"data/js/swfobject.js",
"data/js/swfaddress.js",
"data/js/jquery-1.4.2.min.js",
"data/js/embed.js",
"data/js/korsakow.js",
"data/css/cross.png",
"data/css/embed.css",
};
public static final SoundFormat SOUND_EXPORT_FORMAT = SoundFormat.MP3;
public static final VideoCodec VIDEO_EXPORT_FORMAT = VideoCodec.FLV;
public static final FontFormat FONT_EXPORT_FORMAT = FontFormat.SWF;
public static final String VIDEO_DIR = "video";
public static final String TEXT_DIR = "text";
public static final String IMAGE_DIR = "image";
public static final String SOUND_DIR = "sound";
public static final String FONT_DIR = "font";
public static final String SUBTITLE_DIR = "subtitle";
public static final String THUMBNAIL_DIR = "thumbnail";
private final ExportOptions exportOptions = new ExportOptions();
private Map<String, String> filenamemap;
private File rootDir;
private ISettings settings;
private IProject projectToExport;
private String indexFilename = FLASH_PLAYER_RESOURCE_INDEX;
private Collection<ISnu> snusToExport = new HashSet<ISnu>();
private Collection<ISound> soundsToExport = new HashSet<ISound>();
private Collection<IImage> imagesToExport = new HashSet<IImage>();
private Collection<IVideo> videosToExport = new HashSet<IVideo>();
private Collection<IInterface> interfacesToExport = new HashSet<IInterface>();
private Collection<IText> textsToExport = new HashSet<IText>();
private Collection<Font> fontsToExport = new HashSet<Font>();
private boolean exportFonts = false;
private boolean randomizeDataFilename = false;
private IVideoEncodingProfile videoEncodingProfile;
public Exporter()
{
}
/**
* CAVEAT: must synchronize on the returned instance
* @return
*/
public ExportOptions getExportOptions()
{
return exportOptions;
}
public void setFilenameMap(Map<String, String> map)
{
filenamemap = map;
}
private String getFilename(String key) throws ExportException
{
if (!filenamemap.containsKey(key))
throw new ExportException("unexpected error: filename '" + key + "' not in map", rootDir);
return filenamemap.get(key);
}
public void setRandomizeDataFilename(boolean randomize)
{
randomizeDataFilename = randomize;
}
public void setExportFonts(boolean exportFonts)
{
this.exportFonts = exportFonts;
}
public void setIndexFilename(String indexFilename)
{
this.indexFilename = indexFilename;
}
public void setProject(IProject proj)
{
projectToExport = proj;
}
public void setSettings(ISettings settings)
{
this.settings = settings;
}
public void setSnus(Collection<ISnu> snus)
{
snusToExport = new HashSet<ISnu>(snus);
}
public void setSounds(Collection<ISound> sounds)
{
soundsToExport = new HashSet<ISound>(sounds);
}
public void setImages(Collection<IImage> images)
{
imagesToExport = new HashSet<IImage>(images);
}
public void setVideos(Collection<IVideo> videos)
{
videosToExport = new HashSet<IVideo>(videos);
}
public void setInterfaces(Collection<IInterface> interfaces)
{
interfacesToExport = new HashSet<IInterface>(interfaces);
}
public void setTexts(Collection<IText> texts)
{
textsToExport = new HashSet<IText>(texts);
}
public IVideoEncodingProfile getVideoEncodingProfile()
{
return videoEncodingProfile;
}
public void setVideoEncodingProfile(IVideoEncodingProfile videoEncodingProfile)
{
this.videoEncodingProfile = videoEncodingProfile;
}
public List<ITask> createExportTasks(File rootDir) throws IOException, ExportException, InterruptedException
{
//List<IVideo> videos = Command.listVideo();
IProject project = projectToExport;
FileUtil.mkdirs(rootDir);
File dataDir = new File(rootDir, DATA_DIR);
FileUtil.mkdirs(dataDir);
try {
fontsToExport = calculateFontsToExport(exportFonts, textsToExport, interfacesToExport);
} catch (Exception e) {
throw new ExportException(e, dataDir);
}
String dataPath = "project.xml";
if (randomizeDataFilename)
dataPath = String.format("project%8d.xml", new Random().nextInt());
ITask createFilenameMapTask = new CreateFilenameMapTask(this, IMAGE_DIR, imagesToExport, VIDEO_DIR, videosToExport, SOUND_DIR, soundsToExport, TEXT_DIR, textsToExport, FONT_DIR);
try {
createFilenameMapTask.run();
} catch (TaskException e) {
ExportException ee;
if (e.getCause() instanceof ExportException)
throw ee = (ExportException)e.getCause();
else
ee = new ExportException(e.getCause(), rootDir);
throw ee;
}
List<ITask> exportTasks = new ArrayList<ITask>();
exportTasks.addAll(createFontExportTasks(this, dataDir, fontsToExport));
exportTasks.addAll(createTextExportTasks(exportOptions, dataDir, textsToExport));
if (settings.getBoolean(Settings.ExportImages))
exportTasks.addAll(createImageExportTasks(exportOptions, dataDir, imagesToExport));
if (settings.getBoolean(Settings.ExportSounds))
exportTasks.addAll(createSoundExportTasks(exportOptions, dataDir, soundsToExport));
Dimension maxVideoSize = calculateMaxVideoSize(project, interfacesToExport);
Logger.getLogger(Exporter.class).info(String.format("Video Encoding Profile: %s", videoEncodingProfile.getName()));
if (settings.getBoolean(Settings.ExportVideos))
exportTasks.addAll(createVideoExportTasks(exportOptions, dataDir, videoEncodingProfile, maxVideoSize, videosToExport));
if (settings.getBoolean(Settings.ExportSubtitles))
exportTasks.addAll(createSubtitleExportTasks(exportOptions, dataDir, videosToExport));
// for hackish reasons we check the ExportImages settings inside the createThumbnail task
exportTasks.addAll(createThumbnailExportTasks(exportOptions, rootDir, maxVideoSize, snusToExport));
if (settings.getBoolean(Settings.ExportWebFiles))
exportTasks.add(new CopyPlayerExportTask(rootDir, indexFilename, dataPath, project));
exportTasks.addAll(createXMLExportTasks(dataPath, project, snusToExport, textsToExport, imagesToExport, soundsToExport, videosToExport, interfacesToExport, fontsToExport, dataDir, filenamemap));
return exportTasks;
}
public Map<String, String> getFilenameMap()
{
return filenamemap;
}
/**
* Calculates the largest dimension a video would play at. Currently this means the
* largest size of any video playing widget.
*
* @param interfacesToExport
* @return
*/
private static Dimension calculateMaxVideoSize(IProject projectToExport, Collection<IInterface> interfacesToExport)
{
Dimension d = new Dimension(0, 0);
for (IInterface interf : interfacesToExport)
{
Collection<IWidget> widgets = interf.getWidgets();
for (IWidget widget : widgets)
{
if (widget.getWidgetId().equals(WidgetType.MainMedia.getId()) ||
widget.getWidgetId().equals(WidgetType.MediaArea.getId()) ||
widget.getWidgetId().equals(WidgetType.SnuAutoLink.getId()) ||
widget.getWidgetId().equals(WidgetType.SnuFixedLink.getId()))
{
if (widget.getWidth() > d.width)
d.width = widget.getWidth();
if (widget.getHeight() > d.height)
d.height = widget.getHeight();
}
}
}
if (d.width == 0)
d.width = projectToExport.getMovieWidth();
if (d.height == 0)
d.height = projectToExport.getMovieHeight();
return d;
}
private static Collection<Font> calculateFontsToExport(boolean exportFonts, Collection<IText> textsToExport, Collection<IInterface> interfacesToExport) throws Exception
{
Collection<Font> fontsToExport = new HashSet<Font>();
if (!exportFonts)
return fontsToExport;
for (IText text : textsToExport)
fontsToExport.addAll(text.getFonts());
for (IInterface interf : interfacesToExport) {
for (IWidget widget : interf.getWidgets()) {
if (widget.getDynamicProperty("fontFamily") != null) {
Font font = new Font((String)widget.getDynamicProperty("fontFamily"), Font.PLAIN, widget.getDynamicProperty("fontSize")!=null?(Integer)widget.getDynamicProperty("fontSize"):10);
fontsToExport.add(font);
}
}
}
return fontsToExport;
}
private static List<ITask> createXMLExportTasks(String dataPath, IProject project, Collection<ISnu> snusToExport, Collection<IText> textsToExport, Collection<IImage> imagesToExport, Collection<ISound> soundsToExport, Collection<IVideo> videosToExport, Collection<IInterface> interfacesToExport, Collection<Font> fontsToExport, File rootDir, Map<String, String> filenamemap) throws IOException
{
List<ITask> tasks = new ArrayList<ITask>();
XMLExportTask task = new XMLExportTask(dataPath, project, snusToExport, textsToExport, imagesToExport, soundsToExport, videosToExport, interfacesToExport, fontsToExport, rootDir, filenamemap);
tasks.add(task);
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.processingproject"), tasks));
}
private List<ITask> createVideoExportTasks(ExportOptions options, File rootDir, IVideoEncodingProfile encodingProfile, Dimension maxVideoSize, Collection<IVideo> videosToExport) throws IOException, ExportException
{
Set<String> alreadyTasked = new HashSet<String>();
List<ITask> tasks = new ArrayList<ITask>();
for (IVideo video : videosToExport)
{
String dest = getFilename(video.getAbsoluteFilename());
if (!alreadyTasked.add(dest))
continue;
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
VideoExportTask task = new VideoExportTask(options, encodingProfile, video, destFile, rootDir);
if (maxVideoSize != null)
task.setMaxSize(maxVideoSize.width, maxVideoSize.height);
tasks.add(task);
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingvideo"), tasks));
}
private List<ITask> createSubtitleExportTasks(ExportOptions options, File rootDir, Collection<IVideo> videosToExport) throws IOException, ExportException
{
Set<String> alreadyTasked = new HashSet<String>();
List<ITask> tasks = new ArrayList<ITask>();
for (IVideo video : videosToExport)
{
if (video.getSubtitles() == null)
continue;
String subtitles = Media.getAbsoluteFilename(video.getSubtitles());
if (!alreadyTasked.add(subtitles))
continue;
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + getFilename(subtitles));
destFile.getParentFile().mkdirs();
SubtitleExportTask task = new SubtitleExportTask(options, destFile, new File(subtitles));
tasks.add(task);
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingvideo"), tasks));
}
private List<ITask> createThumbnailExportTasks(ExportOptions options, File rootDir, Dimension maxVideoSize, Collection<ISnu> snusToExport) throws IOException, ExportException
{
List<ITask> tasks = new ArrayList<ITask>();
for (ISnu snu : snusToExport)
{
final IMedia mainMedia = snu.getMainMedia();
String mainMediaFile = getFilename(mainMedia.getAbsoluteFilename());
String base = FileUtil.getFilenameWithoutExtension(new File(mainMediaFile).getName());
String ext = FileUtil.getFileExtension(ImageFormat.JPG);
String dest = base + "." + ext;
File destFile = new File(rootDir, Exporter.DATA_DIR + File.separatorChar + Exporter.THUMBNAIL_DIR + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
ThumbnailExportTask task = new ThumbnailExportTask(options, mainMedia, destFile, rootDir);
if (maxVideoSize != null)
task.setMaxSize(maxVideoSize.width, maxVideoSize.height);
// we do all the other work and check this setting here because of how hackish thumbs are
// and we still want them to appear in the xml!
if (settings.getBoolean(Settings.ExportImages))
tasks.add(task);
// hackish: temporarily setting the thumbnail this way
if (filenamemap.containsKey(destFile.getAbsoluteFile()))
throw new ExportException(
"Internal Error: filename already in use "
+ destFile.getAbsolutePath(), rootDir);
filenamemap.put(destFile.getAbsolutePath(), new File(Exporter.THUMBNAIL_DIR + File.separatorChar + dest).getPath());
IImage thumbnail = new Image(DataRegistry.getMaxId(), 0);
thumbnail.setFilename(destFile.getPath());
thumbnail.setName(destFile.getName());
snu.setThumbnail(thumbnail);
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingthumbnail"), tasks));
}
private static List<ITask> createFontExportTasks(Exporter exporter, File rootDir, Collection<Font> fontsToExport) throws ExportException
{
List<ITask> tasks = new ArrayList<ITask>();
if (!exporter.exportFonts)
return tasks;
if (!fontsToExport.isEmpty()) {
String dest = exporter.getFilename("font.swf");
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
//
tasks.add(new FontExportTask(fontsToExport, destFile));
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingfonts"), tasks));
}
private List<ITask> createTextExportTasks(ExportOptions options, File rootDir, Collection<IText> textsToExport) throws IOException, ExportException
{
List<ITask> tasks = new ArrayList<ITask>();
for (IText text : textsToExport)
{
String dest = getFilename(text.getAbsoluteFilename());
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
tasks.add(new TextExportTask(options, text, destFile));
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingtext"), tasks));
}
private List<ITask> createImageExportTasks(ExportOptions options, File rootDir, Collection<IImage> imagesToExport) throws IOException, ExportException
{
List<ITask> tasks = new ArrayList<ITask>();
for (IImage image : imagesToExport)
{
String dest = getFilename(image.getAbsoluteFilename());
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
tasks.add(new ImageExportTask(options, image, destFile));
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingimage"), tasks));
}
private List<ITask> createSoundExportTasks(ExportOptions options, File rootDir, Collection<ISound> soundsToExport) throws IOException, ExportException
{
List<ITask> tasks = new ArrayList<ITask>();
for (ISound sound : soundsToExport)
{
String dest = getFilename(sound.getAbsoluteFilename());
File destFile = new File(rootDir.getAbsolutePath() + File.separatorChar + dest);
destFile.getParentFile().mkdirs();
File subtitleFile = null;
if (sound.getSubtitles() != null)
subtitleFile = new File(rootDir.getAbsolutePath() + File.separatorChar + getFilename(Media.getAbsoluteFilename(sound.getSubtitles())));
tasks.add(new SoundExportTask(options, sound, destFile, subtitleFile));
}
return Util.list(ITask.class, new DelegateTask(LanguageBundle.getString("export.task.encodingsound"), tasks));
}
}