BililiveRecorder/BililiveRecorder.FlvProcessor/FlvMetadata.cs
2021-01-01 14:46:27 +08:00

337 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace BililiveRecorder.FlvProcessor
{
public class FlvMetadata : IFlvMetadata
{
private IDictionary<string, object> Meta { get; set; } = new Dictionary<string, object>();
public ICollection<string> Keys => this.Meta.Keys;
public ICollection<object> Values => this.Meta.Values;
public int Count => this.Meta.Count;
public bool IsReadOnly => false;
public object this[string key] { get => this.Meta[key]; set => this.Meta[key] = value; }
public FlvMetadata()
{
this.Meta["duration"] = 0.0;
this.Meta["lasttimestamp"] = 0.0;
}
public FlvMetadata(byte[] data)
{
// Meta = _Decode(data);
int readHead = 0;
string name = DecodeScriptDataValue(data, ref readHead, AMFTypes.String) as string;
if (name != "onMetaData")
{
throw new Exception("Isn't onMetadata");
}
this.Meta = DecodeScriptDataValue(data, ref readHead) as Dictionary<string, object>;
if (!this.Meta.ContainsKey("duration"))
{
this.Meta["duration"] = 0d;
}
if (!this.Meta.ContainsKey("lasttimestamp"))
{
this.Meta["lasttimestamp"] = 0d;
}
this.Meta.Remove("");
foreach (var item in this.Meta.ToArray())
{
if (item.Value is string text)
{
this.Meta[item.Key] = text.Replace("\0", "");
}
}
}
public byte[] ToBytes()
{
using (var ms = new MemoryStream())
{
EncodeScriptDataValue(ms, "onMetaData");
EncodeScriptDataValue(ms, this.Meta);
return ms.ToArray();
}
}
#region Static
private static void EncodeScriptDataValue(MemoryStream ms, object value)
{
switch (value)
{
case int number:
{
double asDouble = number;
ms.WriteByte((byte)AMFTypes.Number);
ms.Write(BitConverter.GetBytes(asDouble).ToBE(), 0, sizeof(double));
break;
}
case double number:
{
ms.WriteByte((byte)AMFTypes.Number);
ms.Write(BitConverter.GetBytes(number).ToBE(), 0, sizeof(double));
break;
}
case bool boolean:
{
ms.WriteByte((byte)AMFTypes.Boolean);
if (boolean)
{
ms.WriteByte(1);
}
else
{
ms.WriteByte(0);
}
break;
}
case string text:
{
var b = Encoding.UTF8.GetBytes(text);
if (b.Length >= ushort.MaxValue)
{
ms.WriteByte((byte)AMFTypes.LongString);
ms.Write(BitConverter.GetBytes((uint)b.Length).ToBE(), 0, sizeof(uint));
}
else
{
ms.WriteByte((byte)AMFTypes.String);
ms.Write(BitConverter.GetBytes((ushort)b.Length).ToBE(), 0, sizeof(ushort));
}
ms.Write(b, 0, b.Length);
break;
}
case Dictionary<string, object> d:
{
ms.WriteByte((byte)AMFTypes.Object);
foreach (var item in d)
{
var b = Encoding.UTF8.GetBytes(item.Key);
ms.Write(BitConverter.GetBytes((ushort)b.Length).ToBE(), 0, sizeof(ushort));
ms.Write(b, 0, b.Length);
EncodeScriptDataValue(ms, item.Value);
}
ms.WriteByte(0);
ms.WriteByte(0);
ms.WriteByte(9);
break;
}
case List<object> l:
{
ms.WriteByte((byte)AMFTypes.StrictArray);
ms.Write(BitConverter.GetBytes((uint)l.Count), 0, sizeof(uint));
foreach (var item in l)
{
EncodeScriptDataValue(ms, item);
}
break;
}
case DateTime dateTime:
{
ms.WriteByte((byte)AMFTypes.Date);
ms.Write(BitConverter.GetBytes(dateTime.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds).ToBE(), 0, sizeof(double));
ms.Write(BitConverter.GetBytes((short)0).ToBE(), 0, sizeof(short)); // UTC
break;
}
default:
throw new NotSupportedException("Type " + value.GetType().FullName + " is not supported");
}
}
private static object DecodeScriptDataValue(byte[] buff, ref int _readHead, AMFTypes expectType = AMFTypes.Any)
{
AMFTypes type = (AMFTypes)buff[_readHead++];
if (expectType != AMFTypes.Any && expectType != type)
{
throw new Exception("AMF Decode type error");
}
switch (type)
{
case AMFTypes.Number:
{
const int SIZEOF_DOUBLE = sizeof(double);
byte[] bytes = new byte[SIZEOF_DOUBLE];
Buffer.BlockCopy(buff, _readHead, bytes, 0, bytes.Length);
_readHead += SIZEOF_DOUBLE;
return BitConverter.ToDouble(bytes.ToBE(), 0);
}
case AMFTypes.Boolean:
return buff[_readHead++] != 0;
case AMFTypes.String:
{
byte[] bytes = new byte[sizeof(ushort)];
bytes[0] = buff[_readHead++];
bytes[1] = buff[_readHead++];
ushort text_size = BitConverter.ToUInt16(bytes.ToBE(), 0);
string text = Encoding.UTF8.GetString(buff, _readHead, text_size);
_readHead += text_size;
return text;
}
case AMFTypes.Object:
{
var d = new Dictionary<string, object>();
while (!(buff[_readHead] == 0 && buff[_readHead + 1] == 0 && buff[_readHead + 2] == 9))
{
string key;
{
byte[] bytes = new byte[sizeof(ushort)];
bytes[0] = buff[_readHead++];
bytes[1] = buff[_readHead++];
ushort text_size = BitConverter.ToUInt16(bytes.ToBE(), 0);
key = Encoding.UTF8.GetString(buff, _readHead, text_size);
_readHead += text_size;
}
object value = DecodeScriptDataValue(buff, ref _readHead);
// d.Add(key, value);
d[key] = value; // fix duplicates
}
_readHead += 3;
return d;
}
case AMFTypes.Null:
case AMFTypes.Undefined:
return null;
case AMFTypes.ECMAArray:
_readHead += 4;
goto case AMFTypes.Object;
case AMFTypes.StrictArray:
{
byte[] bytes = new byte[sizeof(uint)];
bytes[0] = buff[_readHead++];
bytes[1] = buff[_readHead++];
bytes[2] = buff[_readHead++];
bytes[3] = buff[_readHead++];
uint array_size = BitConverter.ToUInt32(bytes.ToBE(), 0);
var d = new List<object>();
while (d.Count < array_size)
{
d.Add(DecodeScriptDataValue(buff, ref _readHead));
}
return d;
}
case AMFTypes.Date:
{
const int SIZEOF_DOUBLE = sizeof(double);
const int SIZEOF_SI16 = sizeof(short);
byte[] datetime_bytes = new byte[SIZEOF_DOUBLE];
Buffer.BlockCopy(buff, _readHead, datetime_bytes, 0, datetime_bytes.Length);
_readHead += SIZEOF_DOUBLE;
var datetime = BitConverter.ToDouble(datetime_bytes.ToBE(), 0);
byte[] offset_bytes = new byte[SIZEOF_SI16];
Buffer.BlockCopy(buff, _readHead, offset_bytes, 0, offset_bytes.Length);
_readHead += SIZEOF_SI16;
var offset = BitConverter.ToDouble(offset_bytes.ToBE(), 0);
return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(datetime).AddMinutes(-offset);
}
case AMFTypes.LongString:
unchecked
{
byte[] bytes = new byte[sizeof(uint)];
bytes[0] = buff[_readHead++];
bytes[1] = buff[_readHead++];
bytes[2] = buff[_readHead++];
bytes[3] = buff[_readHead++];
int text_size = (int)BitConverter.ToUInt32(bytes.ToBE(), 0);
if (text_size <= 0)
{
throw new Exception("LongString longer than " + int.MaxValue + " is not supported");
}
string text = Encoding.UTF8.GetString(buff, _readHead, text_size);
_readHead += text_size;
return text;
}
default:
case AMFTypes.MovieClip:
case AMFTypes.Reference:
case AMFTypes.ObjectEndMarker:
throw new NotSupportedException("AMF type not supported");
}
}
#endregion
public void Add(string key, object value)
{
this.Meta.Add(key, value);
}
public bool ContainsKey(string key)
{
return this.Meta.ContainsKey(key);
}
public bool Remove(string key)
{
return this.Meta.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
return this.Meta.TryGetValue(key, out value);
}
public void Add(KeyValuePair<string, object> item)
{
this.Meta.Add(item);
}
public void Clear()
{
this.Meta.Clear();
}
public bool Contains(KeyValuePair<string, object> item)
{
return this.Meta.Contains(item);
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
this.Meta.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<string, object> item)
{
return this.Meta.Remove(item);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return this.Meta.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.Meta.GetEnumerator();
}
}
}