MODULE Codecs; (** AUTHOR "TF"; PURPOSE "CODEC repository"; *)

IMPORT
	Streams, Commands, Files, SoundDevices, Raster, Modules, Strings, Configuration, Unzip, Texts, Archives;

CONST
	ResFailed* = -1;
	ResOk* = 0;
	ResSeekInexact* = 1;

	ImgFmtBW* = 0;
	ImgFmtGrey* = 1;
	ImgFmtRGB* = 2;
	ImgFmtRGBA* = 3;

	STError* = -1;		(* e.g. when requested stream does not exist *)
	STUnknown* = 0;
	STAudio* = 1;
	STVideo* = 2;
	STImage* = 3;

	SeekByte* = 0;
	SeekSample* = 1;
	SeekKeySample* = 2;
	SeekFrame* = 3;
	SeekKeyFrame* = 4;

TYPE
	AVStreamInfo* = RECORD
		streamType* : LONGINT;
		seekability* : SET;
		contentType* : ARRAY 16 OF CHAR;

		length* : LONGINT;
		frames* : LONGINT;
		rate*: LONGINT;
	END;

	FileInputStream* = OBJECT(Streams.Reader)
	VAR
		r : Files.Rider;
		f* : Files.File;
		streamInfo*: AVStreamInfo;

		PROCEDURE Receive(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
		BEGIN
			f.ReadBytes(r, buf, ofs, size);
			len := size - r.res;
			IF len >= min THEN res := Streams.Ok ELSE res := Streams.EOF (* end of file *) END
		END Receive;

		PROCEDURE &InitFileReader*(f : Files.File; pos: LONGINT);
		BEGIN
			InitReader(SELF.Receive, 4096);
			SELF.f := f;
			f.Set(r, pos);
			streamInfo.seekability := {SeekByte};
		END InitFileReader;

		PROCEDURE CanSetPos*(): BOOLEAN;
		BEGIN
			RETURN TRUE;
		END CanSetPos;

		PROCEDURE SetPos*(pos : LONGINT);
		BEGIN
			f.Set(r, pos);
			Reset;
			received := pos;
		END SetPos;
	END FileInputStream;

	AVDemultiplexer* = OBJECT

		(** open the demultiplexer on an input stream *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE GetNumberOfStreams*() : LONGINT;
		BEGIN
			RETURN 0
		END GetNumberOfStreams;

		PROCEDURE GetStreamType*(streamNr : LONGINT): LONGINT;
		BEGIN
			RETURN -1;
		END GetStreamType;

		PROCEDURE GetStreamInfo*(streamNr : LONGINT): AVStreamInfo;
		END GetStreamInfo;

		(* get stream streamNr *)
		PROCEDURE GetStream*(streamNr: LONGINT): DemuxStream;
		END GetStream;

		(* read data from streamNr, store it into buffer buf starting at offset ofs, store size bytes if possible, block if not read min bytes at least. Return number of read bytes in len and return code res *)
		(* this procedure should not be directly called - it is called by the DemuxStream object! *)
		PROCEDURE GetData*(streamNr : LONGINT; VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
		END GetData;

		(* seek the streamNr to position pos (defined bz seekType), res = 0 if Ok, otherwise an error number *)
		(* this procedure should not be directly called - it is called by the DemuxStream object! *)
		PROCEDURE SetStreamPos*(streamNr : LONGINT; seekType : LONGINT; pos : LONGINT; VAR itemSize : LONGINT; VAR res : LONGINT);
		END SetStreamPos;

	END AVDemultiplexer;

	DemuxStream* = OBJECT(Streams.Reader)
	VAR
		demultiplexer* : AVDemultiplexer;
		streamNr* : LONGINT;
		streamInfo* : AVStreamInfo;

		PROCEDURE& Open*(demultiplexer : AVDemultiplexer; streamNr : LONGINT);
		BEGIN
			SELF.demultiplexer := demultiplexer;
			SELF.streamNr := streamNr;
			InitReader(Receive, 4096)
		END Open;

		PROCEDURE Receive(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
		BEGIN
			demultiplexer.GetData(streamNr, buf, ofs, size, min, len, res)
		END Receive;

		PROCEDURE SetPos*(pos : LONGINT);
		VAR  seekType, itemSize, res: LONGINT;
		BEGIN
			seekType := SeekByte;
			demultiplexer.SetStreamPos(streamNr, seekType, pos, itemSize, res);
			Reset
		END SetPos;

		(* seek the streamNr to position pos with seekType. itemSize contains the size of the element seeked to, if known and applicable; res = 0 if Ok, otherwise an error number *)
		PROCEDURE SetPosX*(seekType : LONGINT; pos : LONGINT; VAR itemSize : LONGINT; VAR res : LONGINT);
		BEGIN
			demultiplexer.SetStreamPos(streamNr, seekType, pos, itemSize, res);
			Reset
		END SetPosX;
	END DemuxStream;

	AudioDecoder* = OBJECT
		(* open the decoder on a file *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE HasMoreData*():BOOLEAN;
		END HasMoreData;

		PROCEDURE GetAudioInfo*(VAR nofChannels, samplesPerSecond, bitsPerSample : LONGINT);
		END GetAudioInfo;

		PROCEDURE SetAudioInfo*(nofChannels, samplesPerSecond, bitsPerSample : LONGINT);
		END SetAudioInfo;

		PROCEDURE CanSeek*() : BOOLEAN;
		BEGIN RETURN FALSE
		END CanSeek;

		PROCEDURE GetCurrentSample*() : LONGINT;
		BEGIN HALT(301); RETURN 0
		END GetCurrentSample;

		PROCEDURE GetTotalSamples*() : LONGINT;
		BEGIN HALT(301); RETURN 0
		END GetTotalSamples;

		PROCEDURE GetCurrentTime*() : LONGINT;
		BEGIN HALT(301); RETURN 0
		END GetCurrentTime;

		PROCEDURE SetStreamLength*(length : LONGINT);
		END SetStreamLength;

		PROCEDURE SeekSample*(sample: LONGINT; goKeySample : BOOLEAN; VAR res : LONGINT);
		END SeekSample;

		PROCEDURE SeekMillisecond*(millisecond : LONGINT; goKeySample : BOOLEAN; VAR res : LONGINT);
		END SeekMillisecond;

		(** Prepare the next audio bytes not yet filled into a buffer *)
		PROCEDURE Next*;
		END Next;

		PROCEDURE FillBuffer*(buffer : SoundDevices.Buffer);
		END FillBuffer;

	END AudioDecoder;

	AudioEncoder* = OBJECT
		(* open the encoder *)
		PROCEDURE Open*(out : Streams.Writer; sRate, sRes, nofCh: LONGINT; VAR res : LONGINT);
		END Open;

		PROCEDURE Write*(buffer : SoundDevices.Buffer; VAR res : LONGINT);
		END Write;

		PROCEDURE Close*(VAR res : LONGINT);
		END Close;

	END  AudioEncoder;

	VideoDecoder* = OBJECT
		(* open the decoder on a file *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE HasMoreData*():BOOLEAN;
		END HasMoreData;

		PROCEDURE GetVideoInfo*(VAR width, height, millisecondsPerFrame : LONGINT);
		END GetVideoInfo;

		PROCEDURE CanSeek*() : BOOLEAN;
		BEGIN RETURN FALSE
		END CanSeek;

		PROCEDURE GetCurrentFrame*() : LONGINT;
		END GetCurrentFrame;

		PROCEDURE GetCurrentTime*() : LONGINT;
		END GetCurrentTime;

		PROCEDURE SeekFrame*(frame : LONGINT; goKeyFrame : BOOLEAN; VAR res : LONGINT);
		END SeekFrame;

		PROCEDURE SeekMillisecond*(millisecond : LONGINT; goKeyFrame : BOOLEAN; VAR res : LONGINT);
		END SeekMillisecond;

		(** Prepare the next frame *)
		PROCEDURE Next*;
		END Next;

		PROCEDURE Render*(img : Raster.Image);
		END Render;

	END VideoDecoder;

	ImageDecoder* = OBJECT
		(* open the decoder on an InputStream *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE GetImageInfo*(VAR width, height, format, maxProgressionLevel : LONGINT);
		END GetImageInfo;

		(** Render will read and decode the image data up to progrssionLevel.
			If the progressionLevel is lower than a previously rendered progressionLevel,
			the new level can be ignored by the decoder. If no progressionLevel is set with
			SetProgressionLevel, the level is assumed to be maxProgressionLevel of the image,
			which corresponds to best image quality.
		 *)
		PROCEDURE SetProgressionLevel*(progressionLevel: LONGINT);
		END SetProgressionLevel;

		(* return the image in Raster format that best matches the format *)
		PROCEDURE GetNativeImage*(VAR img : Raster.Image);
		END GetNativeImage;

		(* renders the image into the given Raster.Image at the given progressionLevel *)
		PROCEDURE Render*(img : Raster.Image);
		END Render;

	END ImageDecoder;


	ImageEncoder* = OBJECT
		(* open the encoder on a stream*)
		PROCEDURE Open*(out : Streams.Writer);
		END Open;

		PROCEDURE SetQuality*(quality : LONGINT);
		END SetQuality;

		PROCEDURE WriteImage*(img : Raster.Image; VAR res : LONGINT);
		END WriteImage;

	END ImageEncoder;


	TextDecoder* = OBJECT
		(* open the decoder on an InputStream *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE GetText*() : Texts.Text;
		BEGIN
			HALT(301); RETURN NIL
		END GetText;
	END TextDecoder;

	TextEncoder* = OBJECT
		(* open the encoder on a stream*)
		PROCEDURE Open*(out : Streams.Writer);
		END Open;

		PROCEDURE WriteText*(text : Texts.Text; VAR res : LONGINT);
		END WriteText;
	END TextEncoder;

	CryptoDecoder* = OBJECT
		PROCEDURE Open*(in: Streams.Reader; VAR res: LONGINT);
		END Open;

		PROCEDURE GetReader*(): Streams.Reader;
		END GetReader;
	END CryptoDecoder;

	CryptoEncoder* = OBJECT
		PROCEDURE Open*(out: Streams.Writer);
		END Open;

		PROCEDURE GetWriter*(): Streams.Writer;
		END GetWriter;
	END CryptoEncoder;

(****** Animations *)

CONST
	(** ImageDescriptor.disposeMode *)
	Unspecified* = 0;
	DoNotDispose* = 1;
	RestoreToBackground* = 2;
	RestoreToPrevious* = 3;

	(** ImageDescriptor.flags *)
	WaitForUserInput* = 0;

TYPE

	ImageDescriptor* = OBJECT
	VAR
		left*, top*, width*, height*  : LONGINT;
		image* : Raster.Image;
		delayTime* : LONGINT; (* in milliseconds *)
		disposeMode* : LONGINT;
		flags* : SET;
		previous*, next* : ImageDescriptor;

		PROCEDURE &Init*;
		BEGIN
			left := 0; top := 0; width := 0; height := 0;
			image := NIL;
			delayTime := 20; disposeMode := Unspecified;
			flags := {};
			previous := NIL; next := NIL;
		END Init;

	END ImageDescriptor;

	ImageSequence* = RECORD
		width*, height* : LONGINT;
		bgColor* : LONGINT;
		images* : ImageDescriptor;
	END;

	AnimationDecoder* = OBJECT

		(* open the decoder on an InputStream *)
		PROCEDURE Open*(in : Streams.Reader; VAR res : LONGINT);
		END Open;

		PROCEDURE GetImageSequence*(VAR sequence : ImageSequence; VAR res : LONGINT);
		END GetImageSequence;

	END AnimationDecoder;

TYPE

	DemuxFactory = PROCEDURE () : AVDemultiplexer;

	AudioDecoderFactory = PROCEDURE () : AudioDecoder;
	AudioEncoderFactory = PROCEDURE () : AudioEncoder;

	VideoDecoderFactory = PROCEDURE () : VideoDecoder;

	ImageDecoderFactory = PROCEDURE () : ImageDecoder;
	ImageEncoderFactory = PROCEDURE () : ImageEncoder;

	TextDecoderFactory = PROCEDURE () : TextDecoder;
	TextEncoderFactory = PROCEDURE () : TextEncoder;

	CryptoDecoderFactory = PROCEDURE () : CryptoDecoder;
	CryptoEncoderFactory = PROCEDURE () : CryptoEncoder;

	AnimationDecoderFactory = PROCEDURE () : AnimationDecoder;


PROCEDURE GetDemuxFactoryName(CONST name : ARRAY OF CHAR; VAR module , procedure : Modules.Name; VAR res : LONGINT);
VAR config, factoryName, msg : ARRAY 128 OF CHAR;
BEGIN
	res := ResFailed;
	config := "Codecs.Demultiplexer."; Strings.Append(config, name);
	Configuration.Get(config, factoryName, res);
	IF (res = Configuration.Ok) THEN
		Commands.Split(factoryName, module, procedure, res, msg);
	END
END GetDemuxFactoryName;

PROCEDURE GetDecoderFactoryName(CONST type, name : ARRAY OF CHAR; VAR module, procedure : Modules.Name; VAR res : LONGINT);
VAR config, factoryName, msg : ARRAY 128 OF CHAR;
BEGIN
	res := ResFailed;
	config := "Codecs.Decoder."; Strings.Append(config, type); Strings.Append(config, ".");
	Strings.Append(config, name);
	Configuration.Get(config, factoryName, res);
	IF (res = Configuration.Ok) THEN
		Commands.Split(factoryName, module, procedure, res, msg);
	END
END GetDecoderFactoryName;

PROCEDURE GetEncoderFactoryName(CONST type, name : ARRAY OF CHAR; VAR module, procedure : Modules.Name; VAR res : LONGINT);
VAR config, factoryName, msg : ARRAY 128 OF CHAR;
BEGIN
	res := ResFailed;
	config := "Codecs.Encoder."; Strings.Append(config, type); Strings.Append(config, ".");
	Strings.Append(config, name);
	Configuration.Get(config, factoryName, res);
	IF (res = Configuration.Ok) THEN
		Commands.Split(factoryName, module, procedure, res, msg);
	END
END GetEncoderFactoryName;

(** Return a registered demultiplexer e.g. "AVI" *)
PROCEDURE GetDemultiplexer*(CONST name : ARRAY OF CHAR) : AVDemultiplexer;
VAR
	demux : AVDemultiplexer; factory : DemuxFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	demux := NIL;
	GetDemuxFactoryName(name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			demux := factory();
		END;
	END;
	RETURN demux;
END GetDemultiplexer;

(** Return a registered image decoder e.g. "JP2", "BMP", "PNG" *)
PROCEDURE GetImageDecoder*(CONST name : ARRAY OF CHAR) : ImageDecoder;
VAR
	decoder : ImageDecoder; factory : ImageDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Image", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetImageDecoder;

(** Return a registered image decoder e.g. "BMP" *)
PROCEDURE GetImageEncoder*(CONST name : ARRAY OF CHAR) : ImageEncoder;
VAR
	encoder : ImageEncoder; factory : ImageEncoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	encoder := NIL;
	GetEncoderFactoryName("Image", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			encoder := factory();
		END;
	END;
	RETURN encoder;
END GetImageEncoder;

(** Return a registered video decoder. The decoder name is typically a FourCC code  e.g. "DivX" *)
PROCEDURE GetVideoDecoder*(CONST name : ARRAY OF CHAR) : VideoDecoder;
VAR
	decoder : VideoDecoder; factory : VideoDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Video", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetVideoDecoder;

(** Return a registered audio decoder e.g. "MP3" *)
PROCEDURE GetAudioDecoder*(CONST name : ARRAY OF CHAR) : AudioDecoder;
VAR
	decoder : AudioDecoder; factory : AudioDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Audio", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetAudioDecoder;

(** Return a registered audio encoder e.g. "WAV" *)
PROCEDURE GetAudioEncoder*(CONST name : ARRAY OF CHAR) : AudioEncoder;
VAR
	encoder : AudioEncoder; factory : AudioEncoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	encoder := NIL;
	GetEncoderFactoryName("Audio", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			encoder := factory();
		END;
	END;
	RETURN encoder;
END GetAudioEncoder;

PROCEDURE GetTextDecoder*(CONST name : ARRAY OF CHAR) : TextDecoder;
VAR
	decoder : TextDecoder; factory : TextDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Text", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetTextDecoder;

(** Return a registered image decoder e.g. "BMP" *)
PROCEDURE GetTextEncoder*(CONST name : ARRAY OF CHAR) : TextEncoder;
VAR
	encoder : TextEncoder; factory : TextEncoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	encoder := NIL;
	GetEncoderFactoryName("Text", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			encoder := factory();
		END;
	END;
	RETURN encoder;
END GetTextEncoder;

(** Return a registered crypto decoder *)
PROCEDURE GetCryptoDecoder*(CONST name : ARRAY OF CHAR) : CryptoDecoder;
VAR
	decoder : CryptoDecoder; factory : CryptoDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Crypto", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetCryptoDecoder;

(** Return a registered crypto encoder *)
PROCEDURE GetCryptoEncoder*(CONST name : ARRAY OF CHAR) : CryptoEncoder;
VAR
	encoder : CryptoEncoder; factory : CryptoEncoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	encoder := NIL;
	GetEncoderFactoryName("Crypto", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			encoder := factory();
		END;
	END;
	RETURN encoder;
END GetCryptoEncoder;

(** Return a registered animation decoder e.g. "GIF", "ANI" *)
PROCEDURE GetAnimationDecoder*(CONST name : ARRAY OF CHAR) : AnimationDecoder;
VAR
	decoder : AnimationDecoder; factory : AnimationDecoderFactory;
	moduleName, procedureName : Modules.Name; res : LONGINT;
BEGIN
	decoder := NIL;
	GetDecoderFactoryName("Animation", name, moduleName, procedureName, res);
	IF (res = ResOk) THEN
		GETPROCEDURE(moduleName, procedureName, factory);
		IF (factory # NIL) THEN
			decoder := factory();
		END;
	END;
	RETURN decoder;
END GetAnimationDecoder;

PROCEDURE SplitName*(CONST  name : ARRAY OF CHAR; VAR protocol, filename : ARRAY OF CHAR);
VAR pos, i : LONGINT;
BEGIN
	pos := Strings.Pos("://", name);
	IF pos >= 0 THEN
		FOR i := 0 TO pos - 1 DO protocol[i] := name[i] END;
		protocol[pos] := 0X;
		INC(pos, 3);	i := 0; WHILE name[pos] # 0X DO filename[i] := name[pos]; INC(pos); INC(i) END;
		filename[i] := 0X
	ELSE
		COPY("", protocol);
		COPY(name, filename)
	END
END SplitName;

PROCEDURE JoinName*(CONST protocol, filename : ARRAY OF CHAR; VAR name : ARRAY OF CHAR);
BEGIN
	IF (protocol # "") THEN
		Strings.Concat(protocol, "://", name); Strings.Concat(name, filename, name);
	ELSE
		COPY(filename, name);
	END;
END JoinName;

PROCEDURE OpenInputStream*(CONST name : ARRAY OF CHAR) : Streams.Reader;
VAR f : Files.File;
	is : FileInputStream;
	inpStream : Streams.Reader;
	r : Streams.Receiver;
	tp, protocol, filename : ARRAY 1024 OF CHAR;
	zf : Unzip.ZipFile;
	entry : Unzip.Entry;
	archive : Archives.Archive;
	res : LONGINT;
BEGIN
	SplitName(name, protocol, filename);
	COPY(protocol, tp); Strings.LowerCase(tp);
	IF protocol = "" THEN
		f := Files.Old(filename);
		IF f = NIL THEN RETURN NIL END;
		NEW(is, f, 0);
		RETURN is
	ELSIF Strings.Match("*.zip", tp) THEN
		f := Files.Old(protocol);
		IF f = NIL THEN RETURN NIL END;
		NEW(zf, f, res);
		IF res = 0 THEN
			entry := zf.FindEntry(filename);
			IF entry # NIL THEN
				zf.OpenReceiver(r, entry, res);
				IF res = 0 THEN
					NEW(inpStream, r, 1024);
					RETURN inpStream
				ELSE RETURN NIL
				END
			ELSE RETURN NIL
			END
		ELSE RETURN NIL
		END
	ELSIF Strings.Match("*.skin", tp) THEN
		archive := Archives.Old(protocol, "skin");
		IF archive = NIL THEN
			RETURN NIL
		ELSE
			archive.Acquire; r := archive.OpenReceiver(filename); archive.Release;
			IF r = NIL THEN
				RETURN NIL
			ELSE
				NEW(inpStream, r, 1024);
				RETURN inpStream
			END
		END
	ELSIF Strings.Match("*.tar", tp) OR Strings.Match("*.rep", tp) THEN
		archive := Archives.Old(protocol, "tar");
		IF archive = NIL THEN
			RETURN NIL
		ELSE
			archive.Acquire; r := archive.OpenReceiver(filename); archive.Release;
			IF r = NIL THEN
				RETURN NIL
			ELSE
				NEW(inpStream, r, 1024);
				RETURN inpStream
			END
		END
	END;
	RETURN NIL
END OpenInputStream;

PROCEDURE OpenOutputStream*(CONST name : ARRAY OF CHAR) : Streams.Writer;
VAR
	file : Files.File; w : Files.Writer;
	writer : Streams.Writer;
	sender : Streams.Sender;
	tp, protocol, filename : ARRAY 1024 OF CHAR;
	archive : Archives.Archive;
BEGIN
	writer := NIL;
	SplitName(name, protocol, filename);
	COPY(protocol, tp); Strings.LowerCase(tp);
	IF protocol = "" THEN
		file := Files.New(filename);
		IF file # NIL THEN
			Files.Register(file);
			NEW(w, file, 0); writer := w;
		END
	ELSIF Strings.Match("*.skin", tp) THEN
		archive := Archives.Old(protocol, "skin");
		IF archive = NIL THEN archive := Archives.New(protocol, "skin"); END;
		IF archive # NIL THEN
			archive.Acquire; sender := archive.OpenSender(filename); archive.Release;
			IF sender # NIL THEN
				NEW(writer, sender, 1024);
			END
		END
	ELSIF Strings.Match("*.tar", tp) OR Strings.Match("*.rep", tp)  THEN
		archive := Archives.Old(protocol, "tar");
		IF archive = NIL THEN archive := Archives.New(protocol, "tar"); END;
		IF archive # NIL THEN
			archive.Acquire; sender := archive.OpenSender(filename); archive.Release;
			IF sender # NIL THEN
				NEW(writer, sender, 1024);
			END
		END
	END;
	RETURN writer;
END OpenOutputStream;

END Codecs.

--------------------------
SystemTools.Free Codecs~