Working on mp4

git-svn-id: https://subtitleedit.googlecode.com/svn/trunk@678 99eadd0c-20b8-1223-b5c4-2a2b2df33de2
This commit is contained in:
niksedk 2011-09-30 19:13:12 +00:00
parent 9b4cc2cda8
commit a0e3220ada
10 changed files with 241 additions and 131 deletions

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<Trak> Tracks { get; private set; }
public readonly Mvhd Mvhd;
public readonly List<Trak> Tracks;
public Moov(FileStream fs, ulong maximumLength)
{
Tracks = new List<Trak>();
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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -4,7 +4,7 @@ using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
{
public class Stbl
public class Stbl : Box
{
public readonly List<string> Texts = new List<string>();
@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
{
/// <summary>
/// http://wiki.multimedia.cx/index.php?title=QuickTime_container
/// </summary>
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")
{