diff --git a/src/Logic/Mp4/Boxes/Box.cs b/src/Logic/Mp4/Boxes/Box.cs new file mode 100644 index 000000000..ce0d47e67 --- /dev/null +++ b/src/Logic/Mp4/Boxes/Box.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes +{ + public class Box + { + internal byte[] buffer; + internal ulong pos; + internal string name; + internal UInt64 size; + + public uint GetUInt(byte[] buffer, int index) + { + return (uint)((buffer[index] << 24) + (buffer[index + 1] << 16) + (buffer[index + 2] << 8) + buffer[index + 3]); + } + + public uint GetUInt(int index) + { + return (uint)((buffer[index] << 24) + (buffer[index + 1] << 16) + (buffer[index + 2] << 8) + buffer[index + 3]); + } + + public UInt64 GetUInt64(int index) + { + return (UInt64)((buffer[index] << 56) + (buffer[index + 1] << 48) + (buffer[index + 2] << 40) + (buffer[index + 3] << 32) + + (buffer[index] << 24) + (buffer[index + 1] << 16) + (buffer[index + 2] << 8) + buffer[index + 3]); + + } + + public int GetWord(int index) + { + return (buffer[index] << 8) + buffer[index + 1]; + } + + public string GetString(int index, int count) + { + return Encoding.UTF8.GetString(buffer, index, count); + } + + public string GetString(byte[] buffer, int index, int count) + { + return Encoding.UTF8.GetString(buffer, index, count); + } + + internal bool InitializeSizeAndName(System.IO.FileStream fs) + { + buffer = new byte[8]; + var bytesRead = fs.Read(buffer, 0, buffer.Length); + if (bytesRead < buffer.Length) + return false; + size = GetUInt(0); + name = GetString(4, 4); + + if (size == 0) + { + size = (UInt64)(fs.Length - fs.Position); + } + if (size == 1) + { + bytesRead = fs.Read(buffer, 0, buffer.Length); + if (bytesRead < buffer.Length) + return false; + size = GetUInt64(0); + } + pos = ((ulong)(fs.Position)) + size - 8; + return true; + } + } +} diff --git a/src/Logic/Mp4/Boxes/Mdhd.cs b/src/Logic/Mp4/Boxes/Mdhd.cs index 6957337c0..85b571dd6 100644 --- a/src/Logic/Mp4/Boxes/Mdhd.cs +++ b/src/Logic/Mp4/Boxes/Mdhd.cs @@ -3,7 +3,7 @@ using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Mdhd + public class Mdhd : Box { public readonly UInt64 CreationTime; @@ -15,32 +15,32 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes public Mdhd(FileStream fs, ulong size) { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); int languageIndex = 20; - int version = b[0]; + int version = buffer[0]; if (version == 0) { - CreationTime = Helper.GetUInt(b, 4); - ModificationTime = Helper.GetUInt(b, 8); - TimeScale = Helper.GetUInt(b, 12); - Duration = Helper.GetUInt(b, 16); - Quality = Helper.GetWord(b, 22); + CreationTime = GetUInt(4); + ModificationTime = GetUInt(8); + TimeScale = GetUInt(12); + Duration = GetUInt(16); + Quality = GetWord(22); } else { - CreationTime = Helper.GetUInt64(b, 4); - ModificationTime = Helper.GetUInt64(b, 12); - TimeScale = Helper.GetUInt(b, 16); - Duration = Helper.GetUInt64(b, 20); + CreationTime = GetUInt64(4); + ModificationTime = GetUInt64(12); + TimeScale = GetUInt(16); + Duration = GetUInt64(20); languageIndex = 24; - Quality = Helper.GetWord(b, 26); + Quality = GetWord(26); } // language code = skip first byte, 5 bytes + 5 bytes + 5 bytes (add 0x60 to get ascii value) - int languageByte = ((b[languageIndex] << 1) >> 3) + 0x60; - int languageByte2 = ((b[languageIndex] & 0x3) << 3) + (b[languageIndex+1] >> 5) + 0x60; - int languageByte3 = (b[languageIndex+1] & 0x1f) + 0x60; + int languageByte = ((buffer[languageIndex] << 1) >> 3) + 0x60; + int languageByte2 = ((buffer[languageIndex] & 0x3) << 3) + (buffer[languageIndex + 1] >> 5) + 0x60; + int languageByte3 = (buffer[languageIndex + 1] & 0x1f) + 0x60; char x = (char)languageByte; char x2 = (char)languageByte2; char x3 = (char)languageByte3; diff --git a/src/Logic/Mp4/Boxes/Mdia.cs b/src/Logic/Mp4/Boxes/Mdia.cs index 29c6400d6..577f2568b 100644 --- a/src/Logic/Mp4/Boxes/Mdia.cs +++ b/src/Logic/Mp4/Boxes/Mdia.cs @@ -3,26 +3,37 @@ using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Mdia + public class Mdia : Box { - - public Mdhd Mdhd { get; private set; } - public Minf Minf { get; private set; } + public readonly Mdhd Mdhd; + public readonly Minf Minf; public readonly string HandlerType = null; + public bool IsSubtitle + { + get { return HandlerType == "sbtl"; } + + } + + public bool IsVideo + { + get { return HandlerType == "vide"; } + + } + + public bool IsAudio + { + get { return HandlerType == "soun"; } + } + public Mdia(FileStream fs, ulong maximumLength) { - var buffer = new byte[8]; - ulong pos = (ulong)fs.Position; + pos = (ulong)fs.Position; while (fs.Position < (long)maximumLength) { - int bytesRead; fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - if (bytesRead < 8) + if (!InitializeSizeAndName(fs)) return; - string name = Helper.GetString(buffer, 4, 4); - pos = ((ulong)(fs.Position)) + size - 8; if (name == "minf") { UInt32 timeScale = 90000; @@ -32,16 +43,14 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes } else if (name == "hdlr") { - - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - HandlerType = Helper.GetString(b, 8, 4); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + HandlerType = GetString(8, 4); } else if (name == "mdhd") { Mdhd = new Mdhd(fs, size); } - } } diff --git a/src/Logic/Mp4/Boxes/Minf.cs b/src/Logic/Mp4/Boxes/Minf.cs index c89b92fb5..294b2851b 100644 --- a/src/Logic/Mp4/Boxes/Minf.cs +++ b/src/Logic/Mp4/Boxes/Minf.cs @@ -3,29 +3,22 @@ using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Minf + public class Minf : Box { - public Stbl Stbl { get; private set; } + public readonly Stbl Stbl; public Minf(FileStream fs, ulong maximumLength, UInt32 timeScale) { - var buffer = new byte[8]; - ulong pos = (ulong)fs.Position; + pos = (ulong)fs.Position; while (fs.Position < (long)maximumLength) { - int bytesRead; fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - if (bytesRead < buffer.Length) + if (!InitializeSizeAndName(fs)) return; - string name = Helper.GetString(buffer, 4, 4); - pos = ((ulong)(fs.Position)) + size - 8; if (name == "stbl") - { Stbl = new Stbl(fs, pos, timeScale); - } } } diff --git a/src/Logic/Mp4/Boxes/Moov.cs b/src/Logic/Mp4/Boxes/Moov.cs index 0a11be2fb..106c9fd67 100644 --- a/src/Logic/Mp4/Boxes/Moov.cs +++ b/src/Logic/Mp4/Boxes/Moov.cs @@ -1,37 +1,27 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Moov + public class Moov : Box { - public Mvhd Mvhd { get; private set; } - public List Tracks { get; private set; } + public readonly Mvhd Mvhd; + public readonly List Tracks; public Moov(FileStream fs, ulong maximumLength) { Tracks = new List(); - - var buffer = new byte[8]; - ulong pos = (ulong) fs.Position; + pos = (ulong) fs.Position; while (fs.Position < (long)maximumLength) { - int bytesRead; fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - if (bytesRead < 8) + if (!InitializeSizeAndName(fs)) return; - string name = Helper.GetString(buffer, 4, 4); - pos = ((ulong)(fs.Position)) + size - 8; + if (name == "trak") - { Tracks.Add(new Trak(fs, pos)); - } else if (name == "mvhd") - { Mvhd = new Mvhd(fs, pos); - } } } } diff --git a/src/Logic/Mp4/Boxes/Mvhd.cs b/src/Logic/Mp4/Boxes/Mvhd.cs index b56a31dbd..f5d043986 100644 --- a/src/Logic/Mp4/Boxes/Mvhd.cs +++ b/src/Logic/Mp4/Boxes/Mvhd.cs @@ -1,17 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; +using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Mvhd + public class Mvhd : Box { + public readonly uint Duration; + public readonly uint TimeScale; + public Mvhd(FileStream fs, ulong maximumLength) { - var buffer = new byte[8]; - long pos = fs.Position; + buffer = new byte[20]; + int bytesRead = fs.Read(buffer, 0, buffer.Length); + if (bytesRead < buffer.Length) + return; + + uint TimeScale = GetUInt(12); + uint Duration = GetUInt(16); } } diff --git a/src/Logic/Mp4/Boxes/Stbl.cs b/src/Logic/Mp4/Boxes/Stbl.cs index ac13273a1..f0f3cec15 100644 --- a/src/Logic/Mp4/Boxes/Stbl.cs +++ b/src/Logic/Mp4/Boxes/Stbl.cs @@ -4,7 +4,7 @@ using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Stbl + public class Stbl : Box { public readonly List Texts = new List(); @@ -13,38 +13,33 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes public Stbl(FileStream fs, ulong maximumLength, UInt32 timeScale) { - var buffer = new byte[8]; - ulong pos = (ulong)fs.Position; + pos = (ulong)fs.Position; while (fs.Position < (long)maximumLength) { - int bytesRead; fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - if (bytesRead < buffer.Length) + if (!InitializeSizeAndName(fs)) return; - string name = Helper.GetString(buffer, 4, 4); - pos = ((ulong)(fs.Position)) + size - 8; if (name == "stco") // 32-bit { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - int version = b[0]; - uint totalEntries = Helper.GetUInt(b, 4); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + int version = buffer[0]; + uint totalEntries = GetUInt(4); uint lastOffset = 0; for (int i = 0; i < totalEntries; i++) { - uint offset = Helper.GetUInt(b, 8 + i * 4); + uint offset = GetUInt(8 + i * 4); if (lastOffset + 5 < offset) { fs.Seek(offset, SeekOrigin.Begin); byte[] data = new byte[150]; fs.Read(data, 0, data.Length); - uint textSize = Helper.GetUInt(data, 0); + uint textSize = GetUInt(data, 0); if (textSize < data.Length - 4) { - string text = Helper.GetString(data, 4, (int)textSize - 1); + string text = GetString(data, 4, (int)textSize - 1); Texts.Add(text); } } @@ -53,24 +48,24 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes } else if (name == "co64") // 64-bit { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - int version = b[0]; - uint totalEntries = Helper.GetUInt(b, 4); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + int version = buffer[0]; + uint totalEntries = GetUInt(4); ulong lastOffset = 0; for (int i = 0; i < totalEntries; i++) { - ulong offset = Helper.GetUInt64(b, 8 + i * 8); + ulong offset = GetUInt64(8 + i * 8); if (lastOffset + 8 < offset) { fs.Seek((long)offset, SeekOrigin.Begin); byte[] data = new byte[150]; fs.Read(data, 0, data.Length); - uint textSize = Helper.GetUInt(data, 0); + uint textSize = GetUInt(data, 0); if (textSize < data.Length - 4) { - string text = Helper.GetString(data, 4, (int)textSize - 1); + string text = GetString(data, 4, (int)textSize - 1); Texts.Add(text); } } @@ -79,27 +74,27 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes } else if (name == "stsz") // sample sizes { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - int version = b[0]; - uint uniformSizeOfEachSample = Helper.GetUInt(b, 4); - uint numberOfSampleSizes = Helper.GetUInt(b, 8); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + int version = buffer[0]; + uint uniformSizeOfEachSample = GetUInt(4); + uint numberOfSampleSizes = GetUInt(8); for (int i = 0; i < numberOfSampleSizes; i++) { - uint sampleSize = Helper.GetUInt(b, 12 + i * 4); + uint sampleSize = GetUInt(12 + i * 4); } } else if (name == "stts") // sample table time to sample map { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - int version = b[0]; - uint numberOfSampleTimes = Helper.GetUInt(b, 4); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + int version = buffer[0]; + uint numberOfSampleTimes = GetUInt(4); double totalTime = 0; for (int i = 0; i < numberOfSampleTimes; i++) { - uint sampleCount = Helper.GetUInt(b, 8 + i * 8); - uint sampleDelta = Helper.GetUInt(b, 12 + i * 8); + uint sampleCount = GetUInt(8 + i * 8); + uint sampleDelta = GetUInt(12 + i * 8); totalTime += (double)(sampleDelta / (double)timeScale); if (StartTimeCodes.Count <= EndTimeCodes.Count) StartTimeCodes.Add(totalTime); @@ -109,15 +104,15 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes } else if (name == "stsc") // sample table sample to chunk map { - byte[] b = new byte[size - 4]; - fs.Read(b, 0, b.Length); - int version = b[0]; - uint numberOfSampleTimes = Helper.GetUInt(b, 4); + buffer = new byte[size - 4]; + fs.Read(buffer, 0, buffer.Length); + int version = buffer[0]; + uint numberOfSampleTimes = GetUInt(4); for (int i = 0; i < numberOfSampleTimes; i++) { - uint firstChunk = Helper.GetUInt(b, 8 + i * 12); - uint samplesPerChunk = Helper.GetUInt(b, 12 + i * 12); - uint sampleDescriptionIndex = Helper.GetUInt(b, 16 + i * 12); + uint firstChunk = GetUInt(8 + i * 12); + uint samplesPerChunk = GetUInt(12 + i * 12); + uint sampleDescriptionIndex = GetUInt(16 + i * 12); } } } diff --git a/src/Logic/Mp4/Boxes/Tkhd.cs b/src/Logic/Mp4/Boxes/Tkhd.cs index caea5f33a..fa7eea24f 100644 --- a/src/Logic/Mp4/Boxes/Tkhd.cs +++ b/src/Logic/Mp4/Boxes/Tkhd.cs @@ -1,10 +1,35 @@ using System; using System.Collections.Generic; using System.Text; +using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Tkhd + public class Tkhd : Box { + + public readonly uint Width; + public readonly uint Height; + + public Tkhd(FileStream fs, ulong maximumLength) + { + buffer = new byte[38]; + int bytesRead = fs.Read(buffer, 0, buffer.Length); + if (bytesRead < buffer.Length) + return; + + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 04).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 06).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 08).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 10).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 12).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 14).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 16).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 19).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 20).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 22).ToString()); + //System.Windows.Forms.MessageBox.Show(Helper.GetUInt(buffer, 24).ToString()); + + } } } diff --git a/src/Logic/Mp4/Boxes/Trak.cs b/src/Logic/Mp4/Boxes/Trak.cs index 47dd91d08..2500f59e4 100644 --- a/src/Logic/Mp4/Boxes/Trak.cs +++ b/src/Logic/Mp4/Boxes/Trak.cs @@ -3,29 +3,25 @@ using System.IO; namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes { - public class Trak + public class Trak : Box { - public Mdia Mdia { get; private set; } + public readonly Mdia Mdia; + public readonly Tkhd Tkhd; public Trak(FileStream fs, ulong maximumLength) { - var buffer = new byte[8]; - ulong pos = (ulong)fs.Position; + pos = (ulong)fs.Position; while (fs.Position < (long)maximumLength) { - int bytesRead; fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - if (bytesRead < buffer.Length) + if (!InitializeSizeAndName(fs)) return; - string name = Helper.GetString(buffer, 4, 4); - pos = ((ulong)(fs.Position)) + size - 8; if (name == "mdia") - { Mdia = new Mdia(fs, pos); - } + else if (name == "tkhd") + Tkhd = new Tkhd(fs, pos); } } } diff --git a/src/Logic/Mp4/Mp4Parser.cs b/src/Logic/Mp4/Mp4Parser.cs index 6c2315135..05c2c1440 100644 --- a/src/Logic/Mp4/Mp4Parser.cs +++ b/src/Logic/Mp4/Mp4Parser.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using Nikse.SubtitleEdit.Logic.Mp4.Boxes; -using System; namespace Nikse.SubtitleEdit.Logic.Mp4 { /// /// http://wiki.multimedia.cx/index.php?title=QuickTime_container /// - public class Mp4Parser + public class Mp4Parser : Box { public string FileName { get; private set; } public Moov Moov { get; private set; } @@ -20,7 +20,7 @@ namespace Nikse.SubtitleEdit.Logic.Mp4 { foreach (var trak in Moov.Tracks) { - if (trak.Mdia != null && trak.Mdia.HandlerType == "sbtl" && trak.Mdia.Minf != null && trak.Mdia.Minf.Stbl != null) + if (trak.Mdia != null && trak.Mdia.IsSubtitle && trak.Mdia.Minf != null && trak.Mdia.Minf.Stbl != null) { list.Add(trak); } @@ -29,6 +29,35 @@ namespace Nikse.SubtitleEdit.Logic.Mp4 return list; } + public TimeSpan Duration + { + get + { + if (Moov != null && Moov.Mvhd != null && Moov.Mvhd.TimeScale > 0) + return TimeSpan.FromSeconds((double)Moov.Mvhd.Duration / Moov.Mvhd.TimeScale); + return new TimeSpan(); + } + } + + public System.Drawing.Point VideoResolution + { + get + { + if (Moov != null && Moov.Tracks != null) + { + foreach (var trak in Moov.Tracks) + { + if (trak != null && trak.Mdia != null && trak.Tkhd != null) + { + if (trak.Mdia.IsVideo) + return new System.Drawing.Point((int)trak.Tkhd.Width, (int)trak.Tkhd.Height); + } + } + } + return new System.Drawing.Point(0, 0); + } + } + public Mp4Parser(string fileName) { FileName = fileName; @@ -46,14 +75,13 @@ namespace Nikse.SubtitleEdit.Logic.Mp4 private void ParseMp4(FileStream fs) { int count = 0; - var buffer = new byte[8]; - ulong pos = 0; - int bytesRead = 8; - while (bytesRead == 8) + buffer = new byte[8]; + pos = 0; + bool moreBytes = true; + while (moreBytes) { fs.Seek((long)pos, SeekOrigin.Begin); - ulong size = Helper.ReadSize(buffer, out bytesRead, fs); - string name = Helper.GetString(buffer, 4, 4); + moreBytes = InitializeSizeAndName(fs); pos = ((ulong) (fs.Position)) + size - 8; if (name == "moov") {