All Downloads are FREE. Search and download functionalities are using the official Maven repository.

apparat.swf.Swf.scala Maven / Gradle / Ivy

/*
 * This file is part of Apparat.
 * 
 * Apparat is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Apparat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Apparat. If not, see .
 * 
 * Copyright (C) 2009 Joa Ebert
 * http://www.joa-ebert.com/
 * 
 */
package apparat.swf

import apparat.swc.Swc
import apparat.utils.IO._
import java.io.{
	BufferedInputStream => JBufferedInputStream,
	File => JFile,
	FileInputStream => JFileInputStream,
	FileOutputStream => JFileOutputStream,
	ByteArrayInputStream => JByteArrayInputStream,
	ByteArrayOutputStream => JByteArrayOutputStream,
	InputStream => JInputStream,
	OutputStream => JOutputStream
}
import java.util.zip.{Inflater => JInflater}
import scala.annotation.tailrec
import apparat.utils.{Dumpable, Deflate, IndentingPrintWriter}
import apparat.lzma.LZMA

object Swf {
	def fromFile(file: JFile): Swf = {
		val name = file.getName.toLowerCase

		if(name endsWith ".swc") {
			fromSwc(Swc fromFile file)
		} else if(name endsWith ".swf") {
			val swf = new Swf
			swf read file
			swf
		} else {
			using(new JFileInputStream(file)) {
				input => {
					val b0 = input.read()

					if(('F' == b0 || 'C' == b0) && 'W' == input.read() && 'S' == input.read()) {
						val swf = new Swf
						swf read file
						swf
					} else if ('P' == b0 && 'K' == input.read()) {
						fromSwc(Swc fromFile file)
					} else {
						error("Unknown file "+file.getAbsolutePath+".")
					}
				}
			}
		}
	}

	def fromFile(pathname: String): Swf = fromFile(new JFile(pathname))

	def fromSwc(swc: Swc) = {
		val swf = new Swf
		swf read swc
		swf
	}

	def fromInputStream(input: JInputStream, length: Long) = {
		val swf = new Swf
		swf.read(input, length)
		swf
	}
}

final class Swf extends Dumpable with SwfTagMapping {
	var compressed: Boolean = true
	var version: Int = 10
	var frameSize: Rect = new Rect(0, 20000, 0, 20000)
	var frameRate: Float = 255.0f
	var frameCount: Int = 1
	var tags: List[SwfTag] = Nil

	def width = frameSize._2 / 20
	def height = frameSize._4 / 20

	def foreach(body: SwfTag => Unit) = tags foreach body

	def read(file: JFile): Unit = using(new JBufferedInputStream(new JFileInputStream(file), 0x1000))(read(_, file.length))

	def read(pathname: String): Unit = read(new JFile(pathname))

	def read(input: JInputStream, inputLength: Long): Unit = using(new SwfInputStream(input))(read(_, inputLength))

	def read(data: Array[Byte]): Unit = using(new JByteArrayInputStream(data))(read(_, data.length))

	def read(swc: Swc): Unit = {
		swc.library match {
			case Some(data) => read(data)
			case None =>
		}
	}

	def read(input: SwfInputStream, inputLength: Long): Unit = {
		(input.readUI08(), input.readUI08(), input.readUI08()) match {
			case (x, 'W', 'S') => compressed = x match {
				case 'C' => true
				case 'F' => false
				case _ => error("Not a SWF file.")
			}
			case _ => error("Not a SWF file.")
		}

		version = input.readUI08()

		val uncompressedLength = input.readUI32()
		val uncompressed = compressed match {
			case true => {
				assert(version > 5)
				uncompress(inputLength, uncompressedLength)(input)
			}
			case false => input
		}

		try {
			frameSize = uncompressed.readRECT()
			frameRate = uncompressed.readFIXED8()
			frameCount = uncompressed.readUI16()

			assert(frameSize.minX == 0 && frameSize.minY == 0)
			assert(frameRate >= 0)
			assert(frameCount > 0)

			tags = tagsOf(uncompressed)
		} finally {
			if(compressed) {
				try {
					uncompressed.close()
				} catch {
					case _ =>
				}
			}
		}
	}

	private def tagsOf(implicit input: SwfInputStream): List[SwfTag] = {
		@tailrec def loop(tag: SwfTag, acc: List[SwfTag]): List[SwfTag] = {
			val result = tag :: acc
			if(tag.kind == SwfTags.End) result else loop(input.readTAG(), result)
		}

		loop(input.readTAG(), List.empty).reverse
	}

	def write(file: JFile): Unit = using(new JFileOutputStream(file))(write _)

	def write(pathname: String): Unit = write(new JFile(pathname))

	def write(output: JOutputStream): Unit = using(new SwfOutputStream(output))(write _)

	def write(swc: Swc): Unit = {
		val byteArrayOutputStream = new JByteArrayOutputStream()

		try {
			write(byteArrayOutputStream)
			swc.library = Some(byteArrayOutputStream.toByteArray)
		} finally {
			try {
				byteArrayOutputStream.close()
			} catch {
				case _ =>
			}
		}
	}

	def write(output: SwfOutputStream): Unit = {
		val byteArrayOutputStream = new JByteArrayOutputStream(0x08 + (tags.length << 0x03))
		val buffer = new SwfOutputStream(byteArrayOutputStream)

		try {
			buffer.writeRECT(frameSize)
			buffer.writeFIXED8(frameRate)
			buffer.writeUI16(frameCount)
			buffer.flush()

			tags foreach { buffer writeTAG _ }
			buffer.flush()

			val bytes = byteArrayOutputStream.toByteArray

			buffer.close()

			output.write(Array[Byte](if (compressed) 'C' else 'F', 'W', 'S'))
			output.writeUI08(version)
			output.writeUI32(8 + bytes.length)

			if (compressed) {
				Deflate.compress(bytes, output)
			} else {
				output write bytes
			}

			output.flush()
		} finally {
			if (null != buffer) {
				try {
					buffer.close()
				} catch {
					case _ =>
				}
			}
		}
	}

	def uncompress(inputLength: Long, uncompressedLength: Long)(implicit input: JInputStream) = {
		val totalBytes = (inputLength - 8).asInstanceOf[Int]//magic 8 is static part of header length
		val inflater = new JInflater()
		val bufferIn = new Array[Byte](totalBytes)
		val bufferOut = new Array[Byte]((uncompressedLength - 8).asInstanceOf[Int])

		readBytes(totalBytes, bufferIn)

		inflater setInput (bufferIn)

		var offset = -1
		while (0 != offset && !inflater.finished()) {
			offset = inflater inflate bufferOut
			if (0 == offset && inflater.needsInput) {
				error("Need more input.")
			}
		}

		new SwfInputStream(new JByteArrayInputStream(bufferOut))
	}

	def toByteArray = {
		val byteArrayOutputStream = new JByteArrayOutputStream()
		using(byteArrayOutputStream) { write _ }
		byteArrayOutputStream.toByteArray
	}

	def toLZMAByteArray = {
		val oldCompression = compressed

		try {
			compressed = false

			val swfByteArrayOutputStream = new JByteArrayOutputStream()
			val lzmaByteArrayOutputStream = new JByteArrayOutputStream()

			using(swfByteArrayOutputStream) { write _ }

			val byteArray = swfByteArrayOutputStream.toByteArray

			LZMA.encode(new JByteArrayInputStream(byteArray), byteArray.length, lzmaByteArrayOutputStream)
			lzmaByteArrayOutputStream.toByteArray
		} finally {
			compressed = oldCompression
		}
	}

	override def dump(writer: IndentingPrintWriter) = {
		writer <= "Swf:"
		writer withIndent {
			writer <= "Compressed: "+compressed
			writer <= "Version: "+version
			writer <= "Framesize:"+frameSize
			writer <= "Framerate:"+frameRate
			writer <= "Framecount:"+frameCount
			writer <= "Tags:"
			writer withIndent {
				for(tag <- tags) tag match {
					case dumpable: Dumpable => dumpable dump writer
					case other => writer <= other.toString
				}
			}
		}
	}

	lazy val mainClass = tags find { _.kind == SwfTags.SymbolClass } match {
		case Some(symbolClass: SymbolClass) => symbolClass.symbols find { _._1 == 0 } map { _._2 }
		case _=> None
	}

	lazy val backgroundColor = tags find { _.kind == SwfTags.SetBackgroundColor } match {
		case Some(setBackgroundColor: SetBackgroundColor) => Some(setBackgroundColor.color)
		case _ => None
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy