109 lines
6.3 KiB
C#
109 lines
6.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
namespace Jellyfin.Plugin.Enigma2;
|
|
|
|
/// <summary>Declare silent audio and empty DVB subtitle tracks on status clips with stable transport PIDs.</summary>
|
|
public static class BroadcastSlateProgram
|
|
{
|
|
public static byte[] Apply(byte[] clip, BroadcastTracks tracks, bool clearSubtitles = true)
|
|
{
|
|
if (clip.Length % 188 != 0) throw new InvalidDataException("Unaligned status clip");
|
|
var section = Section(tracks);
|
|
using var output = new MemoryStream(clip.Length + 188 * 8);
|
|
var cc = 0; var written = false; var cleared = false;
|
|
for (var start = 0; start < clip.Length; start += 188)
|
|
{
|
|
var pid = ((clip[start + 1] & 31) << 8) | clip[start + 2];
|
|
if (pid != 4096)
|
|
{
|
|
output.Write(clip, start, 188);
|
|
if (clearSubtitles && !cleared && pid == 256 && (clip[start + 1] & 64) != 0)
|
|
{
|
|
var p = start + 4 + ((clip[start + 3] & 32) != 0 ? clip[start + 4] + 1 : 0);
|
|
if (p + 14 <= start + 188 && clip[p] == 0 && clip[p + 1] == 0 && clip[p + 2] == 1 && (clip[p + 7] & 128) != 0)
|
|
{
|
|
long pts = ((long)(clip[p + 9] & 14) << 29) | ((long)clip[p + 10] << 22) | ((long)(clip[p + 11] & 254) << 14) | ((long)clip[p + 12] << 7) | ((long)clip[p + 13] >> 1);
|
|
for (var i = 0; i < tracks.Output.Length; i++)
|
|
if (tracks.Output[i].Codec == "DVBSUB") output.Write(ClearSubtitle(tracks.Input[i], 256 + i, pts,
|
|
tracks.Input[0].Width ?? ContinuityStreams.Width, tracks.Input[0].Height ?? ContinuityStreams.Height));
|
|
cleared = true;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if ((clip[start + 1] & 64) == 0) continue;
|
|
written = true;
|
|
var offset = 0;
|
|
while (offset < section.Length)
|
|
{
|
|
var packet = new byte[188]; Array.Fill(packet, (byte)255);
|
|
packet[0] = 71; packet[1] = (byte)(16 | (offset == 0 ? 64 : 0)); packet[2] = 0; packet[3] = (byte)(16 | (cc++ & 15));
|
|
var p = 4; if (offset == 0) packet[p++] = 0;
|
|
var count = Math.Min(188 - p, section.Length - offset); section.AsSpan(offset, count).CopyTo(packet.AsSpan(p));
|
|
offset += count; output.Write(packet);
|
|
}
|
|
}
|
|
if (!written) throw new InvalidDataException("Status clip has no expected program map");
|
|
return output.ToArray();
|
|
}
|
|
private static byte[] ClearSubtitle(MediaStream stream, int pid, long pts, int width, int height)
|
|
{
|
|
var extra = (stream as BroadcastMediaStream)?.SubtitleExtraData;
|
|
var page = extra is { Length: >= 2 } ? (extra[0] << 8) | extra[1] : 1;
|
|
var payload = new byte[] { 32, 0,
|
|
15, 20, (byte)(page >> 8), (byte)page, 0, 5, 0, (byte)((width - 1) >> 8), (byte)(width - 1), (byte)((height - 1) >> 8), (byte)(height - 1),
|
|
15, 16, (byte)(page >> 8), (byte)page, 0, 2, 1, 3,
|
|
15, 128, (byte)(page >> 8), (byte)page, 0, 0, 255 };
|
|
var pes = new List<byte> { 0, 0, 1, 189, 0, (byte)(payload.Length + 8), 128, 128, 5,
|
|
(byte)(33L | ((pts >> 29) & 14)), (byte)(pts >> 22), (byte)(1L | ((pts >> 14) & 254)), (byte)(pts >> 7), (byte)(1L | (pts << 1)) };
|
|
pes.AddRange(payload);
|
|
var packet = Enumerable.Repeat((byte)255, 188).ToArray();
|
|
packet[0] = 71; packet[1] = (byte)(64 | (pid >> 8)); packet[2] = (byte)pid; packet[3] = 48;
|
|
packet[4] = (byte)(183 - pes.Count); packet[5] = 0; pes.CopyTo(packet, 188 - pes.Count);
|
|
return packet;
|
|
}
|
|
public static byte[] Section(BroadcastTracks tracks)
|
|
{
|
|
var data = new List<byte> { 2, 176, 0, 0, 1, 193, 0, 0, 225, 0, 240, 0 };
|
|
for (var i = 0; i < tracks.Output.Length; i++)
|
|
{
|
|
var s = tracks.Output[i]; var descriptors = new List<byte>();
|
|
var language = (s.Language ?? "und").Split(',')[0];
|
|
if (language.Length != 3 || language.Any(c => c < 'a' || c > 'z')) language = "und";
|
|
var lang = Encoding.ASCII.GetBytes(language);
|
|
var type = s.Type == MediaStreamType.Video ? 27 : s.Type == MediaStreamType.Audio ? 15 : 6;
|
|
if (s.Type == MediaStreamType.Audio) { descriptors.AddRange(new byte[] { 10, 4 }); descriptors.AddRange(lang); descriptors.Add((byte)(s.IsHearingImpaired ? 2 : 0)); }
|
|
if (s.Type == MediaStreamType.Subtitle)
|
|
{
|
|
var extra = (tracks.Input[i] as BroadcastMediaStream)?.SubtitleExtraData ?? Array.Empty<byte>();
|
|
if (s.Codec == "DVBSUB")
|
|
{
|
|
descriptors.AddRange(new byte[] { 89, 8 }); descriptors.AddRange(lang); descriptors.Add((byte)(s.IsHearingImpaired ? 32 : 16));
|
|
// ffprobe DVB extradata stores composition/ancillary page IDs in big endian.
|
|
descriptors.AddRange(extra.Length >= 4 ? extra.Take(4) : new byte[] { 0, 1, 0, 1 });
|
|
}
|
|
else if (s.Codec == "DVBTXT")
|
|
{
|
|
descriptors.AddRange(new byte[] { 86, 5 }); descriptors.AddRange(lang);
|
|
descriptors.Add(extra.Length >= 2 ? extra[0] : (byte)17); descriptors.Add(extra.Length >= 2 ? extra[1] : (byte)0);
|
|
}
|
|
}
|
|
var pid = 256 + i;
|
|
data.Add((byte)type); data.Add((byte)(224 | (pid >> 8))); data.Add((byte)pid);
|
|
data.Add((byte)(240 | (descriptors.Count >> 8))); data.Add((byte)descriptors.Count); data.AddRange(descriptors);
|
|
}
|
|
var length = data.Count - 3 + 4;
|
|
if (length > 1021) throw new InvalidDataException("Broadcast program map exceeds MPEG-TS limit");
|
|
data[1] = (byte)(176 | (length >> 8)); data[2] = (byte)length;
|
|
uint crc = 0xffffffff;
|
|
foreach (var value in data) { crc ^= (uint)value << 24; for (var bit = 0; bit < 8; bit++) crc = (crc & 0x80000000) != 0 ? (crc << 1) ^ 0x04c11db7 : crc << 1; }
|
|
data.Add((byte)(crc >> 24)); data.Add((byte)(crc >> 16)); data.Add((byte)(crc >> 8)); data.Add((byte)crc);
|
|
return data.ToArray();
|
|
}
|
|
}
|