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 namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
{ {
public class Mdhd public class Mdhd : Box
{ {
public readonly UInt64 CreationTime; public readonly UInt64 CreationTime;
@ -15,32 +15,32 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
public Mdhd(FileStream fs, ulong size) public Mdhd(FileStream fs, ulong size)
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int languageIndex = 20; int languageIndex = 20;
int version = b[0]; int version = buffer[0];
if (version == 0) if (version == 0)
{ {
CreationTime = Helper.GetUInt(b, 4); CreationTime = GetUInt(4);
ModificationTime = Helper.GetUInt(b, 8); ModificationTime = GetUInt(8);
TimeScale = Helper.GetUInt(b, 12); TimeScale = GetUInt(12);
Duration = Helper.GetUInt(b, 16); Duration = GetUInt(16);
Quality = Helper.GetWord(b, 22); Quality = GetWord(22);
} }
else else
{ {
CreationTime = Helper.GetUInt64(b, 4); CreationTime = GetUInt64(4);
ModificationTime = Helper.GetUInt64(b, 12); ModificationTime = GetUInt64(12);
TimeScale = Helper.GetUInt(b, 16); TimeScale = GetUInt(16);
Duration = Helper.GetUInt64(b, 20); Duration = GetUInt64(20);
languageIndex = 24; 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) // 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 languageByte = ((buffer[languageIndex] << 1) >> 3) + 0x60;
int languageByte2 = ((b[languageIndex] & 0x3) << 3) + (b[languageIndex+1] >> 5) + 0x60; int languageByte2 = ((buffer[languageIndex] & 0x3) << 3) + (buffer[languageIndex + 1] >> 5) + 0x60;
int languageByte3 = (b[languageIndex+1] & 0x1f) + 0x60; int languageByte3 = (buffer[languageIndex + 1] & 0x1f) + 0x60;
char x = (char)languageByte; char x = (char)languageByte;
char x2 = (char)languageByte2; char x2 = (char)languageByte2;
char x3 = (char)languageByte3; char x3 = (char)languageByte3;

View File

@ -3,26 +3,37 @@ using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
{ {
public class Mdia public class Mdia : Box
{ {
public readonly Mdhd Mdhd;
public Mdhd Mdhd { get; private set; } public readonly Minf Minf;
public Minf Minf { get; private set; }
public readonly string HandlerType = null; 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) public Mdia(FileStream fs, ulong maximumLength)
{ {
var buffer = new byte[8]; pos = (ulong)fs.Position;
ulong pos = (ulong)fs.Position;
while (fs.Position < (long)maximumLength) while (fs.Position < (long)maximumLength)
{ {
int bytesRead;
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); if (!InitializeSizeAndName(fs))
if (bytesRead < 8)
return; return;
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong)(fs.Position)) + size - 8;
if (name == "minf") if (name == "minf")
{ {
UInt32 timeScale = 90000; UInt32 timeScale = 90000;
@ -32,16 +43,14 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
} }
else if (name == "hdlr") else if (name == "hdlr")
{ {
buffer = new byte[size - 4];
byte[] b = new byte[size - 4]; fs.Read(buffer, 0, buffer.Length);
fs.Read(b, 0, b.Length); HandlerType = GetString(8, 4);
HandlerType = Helper.GetString(b, 8, 4);
} }
else if (name == "mdhd") else if (name == "mdhd")
{ {
Mdhd = new Mdhd(fs, size); Mdhd = new Mdhd(fs, size);
} }
} }
} }

View File

@ -3,29 +3,22 @@ using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes 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) public Minf(FileStream fs, ulong maximumLength, UInt32 timeScale)
{ {
var buffer = new byte[8]; pos = (ulong)fs.Position;
ulong pos = (ulong)fs.Position;
while (fs.Position < (long)maximumLength) while (fs.Position < (long)maximumLength)
{ {
int bytesRead;
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); if (!InitializeSizeAndName(fs))
if (bytesRead < buffer.Length)
return; return;
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong)(fs.Position)) + size - 8;
if (name == "stbl") if (name == "stbl")
{
Stbl = new Stbl(fs, pos, timeScale); 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; using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
{ {
public class Moov public class Moov : Box
{ {
public Mvhd Mvhd { get; private set; } public readonly Mvhd Mvhd;
public List<Trak> Tracks { get; private set; } public readonly List<Trak> Tracks;
public Moov(FileStream fs, ulong maximumLength) public Moov(FileStream fs, ulong maximumLength)
{ {
Tracks = new List<Trak>(); Tracks = new List<Trak>();
pos = (ulong) fs.Position;
var buffer = new byte[8];
ulong pos = (ulong) fs.Position;
while (fs.Position < (long)maximumLength) while (fs.Position < (long)maximumLength)
{ {
int bytesRead;
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); if (!InitializeSizeAndName(fs))
if (bytesRead < 8)
return; return;
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong)(fs.Position)) + size - 8;
if (name == "trak") if (name == "trak")
{
Tracks.Add(new Trak(fs, pos)); Tracks.Add(new Trak(fs, pos));
}
else if (name == "mvhd") else if (name == "mvhd")
{
Mvhd = new Mvhd(fs, pos); Mvhd = new Mvhd(fs, pos);
}
} }
} }
} }

View File

@ -1,17 +1,22 @@
using System; using System.IO;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes 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) public Mvhd(FileStream fs, ulong maximumLength)
{ {
var buffer = new byte[8]; buffer = new byte[20];
long pos = fs.Position; 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 namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
{ {
public class Stbl public class Stbl : Box
{ {
public readonly List<string> Texts = new List<string>(); 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) public Stbl(FileStream fs, ulong maximumLength, UInt32 timeScale)
{ {
var buffer = new byte[8]; pos = (ulong)fs.Position;
ulong pos = (ulong)fs.Position;
while (fs.Position < (long)maximumLength) while (fs.Position < (long)maximumLength)
{ {
int bytesRead;
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); if (!InitializeSizeAndName(fs))
if (bytesRead < buffer.Length)
return; return;
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong)(fs.Position)) + size - 8;
if (name == "stco") // 32-bit if (name == "stco") // 32-bit
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int version = b[0]; int version = buffer[0];
uint totalEntries = Helper.GetUInt(b, 4); uint totalEntries = GetUInt(4);
uint lastOffset = 0; uint lastOffset = 0;
for (int i = 0; i < totalEntries; i++) 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) if (lastOffset + 5 < offset)
{ {
fs.Seek(offset, SeekOrigin.Begin); fs.Seek(offset, SeekOrigin.Begin);
byte[] data = new byte[150]; byte[] data = new byte[150];
fs.Read(data, 0, data.Length); fs.Read(data, 0, data.Length);
uint textSize = Helper.GetUInt(data, 0); uint textSize = GetUInt(data, 0);
if (textSize < data.Length - 4) 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); Texts.Add(text);
} }
} }
@ -53,24 +48,24 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
} }
else if (name == "co64") // 64-bit else if (name == "co64") // 64-bit
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int version = b[0]; int version = buffer[0];
uint totalEntries = Helper.GetUInt(b, 4); uint totalEntries = GetUInt(4);
ulong lastOffset = 0; ulong lastOffset = 0;
for (int i = 0; i < totalEntries; i++) 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) if (lastOffset + 8 < offset)
{ {
fs.Seek((long)offset, SeekOrigin.Begin); fs.Seek((long)offset, SeekOrigin.Begin);
byte[] data = new byte[150]; byte[] data = new byte[150];
fs.Read(data, 0, data.Length); fs.Read(data, 0, data.Length);
uint textSize = Helper.GetUInt(data, 0); uint textSize = GetUInt(data, 0);
if (textSize < data.Length - 4) 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); Texts.Add(text);
} }
} }
@ -79,27 +74,27 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
} }
else if (name == "stsz") // sample sizes else if (name == "stsz") // sample sizes
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int version = b[0]; int version = buffer[0];
uint uniformSizeOfEachSample = Helper.GetUInt(b, 4); uint uniformSizeOfEachSample = GetUInt(4);
uint numberOfSampleSizes = Helper.GetUInt(b, 8); uint numberOfSampleSizes = GetUInt(8);
for (int i = 0; i < numberOfSampleSizes; i++) 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 else if (name == "stts") // sample table time to sample map
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int version = b[0]; int version = buffer[0];
uint numberOfSampleTimes = Helper.GetUInt(b, 4); uint numberOfSampleTimes = GetUInt(4);
double totalTime = 0; double totalTime = 0;
for (int i = 0; i < numberOfSampleTimes; i++) for (int i = 0; i < numberOfSampleTimes; i++)
{ {
uint sampleCount = Helper.GetUInt(b, 8 + i * 8); uint sampleCount = GetUInt(8 + i * 8);
uint sampleDelta = Helper.GetUInt(b, 12 + i * 8); uint sampleDelta = GetUInt(12 + i * 8);
totalTime += (double)(sampleDelta / (double)timeScale); totalTime += (double)(sampleDelta / (double)timeScale);
if (StartTimeCodes.Count <= EndTimeCodes.Count) if (StartTimeCodes.Count <= EndTimeCodes.Count)
StartTimeCodes.Add(totalTime); StartTimeCodes.Add(totalTime);
@ -109,15 +104,15 @@ namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes
} }
else if (name == "stsc") // sample table sample to chunk map else if (name == "stsc") // sample table sample to chunk map
{ {
byte[] b = new byte[size - 4]; buffer = new byte[size - 4];
fs.Read(b, 0, b.Length); fs.Read(buffer, 0, buffer.Length);
int version = b[0]; int version = buffer[0];
uint numberOfSampleTimes = Helper.GetUInt(b, 4); uint numberOfSampleTimes = GetUInt(4);
for (int i = 0; i < numberOfSampleTimes; i++) for (int i = 0; i < numberOfSampleTimes; i++)
{ {
uint firstChunk = Helper.GetUInt(b, 8 + i * 12); uint firstChunk = GetUInt(8 + i * 12);
uint samplesPerChunk = Helper.GetUInt(b, 12 + i * 12); uint samplesPerChunk = GetUInt(12 + i * 12);
uint sampleDescriptionIndex = Helper.GetUInt(b, 16 + i * 12); uint sampleDescriptionIndex = GetUInt(16 + i * 12);
} }
} }
} }

View File

@ -1,10 +1,35 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.IO;
namespace Nikse.SubtitleEdit.Logic.Mp4.Boxes 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 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) public Trak(FileStream fs, ulong maximumLength)
{ {
var buffer = new byte[8]; pos = (ulong)fs.Position;
ulong pos = (ulong)fs.Position;
while (fs.Position < (long)maximumLength) while (fs.Position < (long)maximumLength)
{ {
int bytesRead;
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); if (!InitializeSizeAndName(fs))
if (bytesRead < buffer.Length)
return; return;
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong)(fs.Position)) + size - 8;
if (name == "mdia") if (name == "mdia")
{
Mdia = new Mdia(fs, pos); 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 System.IO;
using Nikse.SubtitleEdit.Logic.Mp4.Boxes; using Nikse.SubtitleEdit.Logic.Mp4.Boxes;
using System;
namespace Nikse.SubtitleEdit.Logic.Mp4 namespace Nikse.SubtitleEdit.Logic.Mp4
{ {
/// <summary> /// <summary>
/// http://wiki.multimedia.cx/index.php?title=QuickTime_container /// http://wiki.multimedia.cx/index.php?title=QuickTime_container
/// </summary> /// </summary>
public class Mp4Parser public class Mp4Parser : Box
{ {
public string FileName { get; private set; } public string FileName { get; private set; }
public Moov Moov { get; private set; } public Moov Moov { get; private set; }
@ -20,7 +20,7 @@ namespace Nikse.SubtitleEdit.Logic.Mp4
{ {
foreach (var trak in Moov.Tracks) 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); list.Add(trak);
} }
@ -29,6 +29,35 @@ namespace Nikse.SubtitleEdit.Logic.Mp4
return list; 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) public Mp4Parser(string fileName)
{ {
FileName = fileName; FileName = fileName;
@ -46,14 +75,13 @@ namespace Nikse.SubtitleEdit.Logic.Mp4
private void ParseMp4(FileStream fs) private void ParseMp4(FileStream fs)
{ {
int count = 0; int count = 0;
var buffer = new byte[8]; buffer = new byte[8];
ulong pos = 0; pos = 0;
int bytesRead = 8; bool moreBytes = true;
while (bytesRead == 8) while (moreBytes)
{ {
fs.Seek((long)pos, SeekOrigin.Begin); fs.Seek((long)pos, SeekOrigin.Begin);
ulong size = Helper.ReadSize(buffer, out bytesRead, fs); moreBytes = InitializeSizeAndName(fs);
string name = Helper.GetString(buffer, 4, 4);
pos = ((ulong) (fs.Position)) + size - 8; pos = ((ulong) (fs.Position)) + size - 8;
if (name == "moov") if (name == "moov")
{ {