mirror of
https://github.com/SubtitleEdit/subtitleedit.git
synced 2024-11-22 19:22:53 +01:00
626 lines
25 KiB
C#
626 lines
25 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Nikse.SubtitleEdit.Core
|
|
{
|
|
public class IfoParser : IDisposable
|
|
{
|
|
public class AudioStream
|
|
{
|
|
public int LanguageTypeSpecified { get; set; }
|
|
public string Language { get; set; }
|
|
public string LanguageCode { get; set; }
|
|
public string CodingMode { get; set; }
|
|
public int Channels { get; set; }
|
|
public string Extension { get; set; }
|
|
};
|
|
|
|
public class VideoStream
|
|
{
|
|
public string Aspect { get; set; }
|
|
public string Standard { get; set; }
|
|
public string CodingMode { get; set; }
|
|
public string Resolution { get; set; }
|
|
}
|
|
|
|
public class VtsVobs
|
|
{
|
|
public int NumberOfAudioStreams { get; set; }
|
|
public int NumberOfSubtitles { get; set; }
|
|
public VideoStream VideoStream { get; set; }
|
|
public List<AudioStream> AudioStreams { get; set; }
|
|
public List<string> Subtitles { get; set; }
|
|
public List<string> SubtitleIDs { get; set; }
|
|
public List<string> SubtitleTypes { get; set; }
|
|
|
|
public List<string> GetAllLanguages()
|
|
{
|
|
var list = new List<string>();
|
|
for (int i = 0; i < Subtitles.Count; i++)
|
|
{
|
|
if (i < SubtitleIDs.Count && i < SubtitleTypes.Count)
|
|
{
|
|
var ids = SubtitleIDs[i].Split(',');
|
|
var types = SubtitleTypes[i].Split(',');
|
|
if (ids.Length == 2 && ids[0].Trim() == ids[1].Trim() || ids.Length == 3 && ids[0].Trim() == ids[1].Trim() && ids[1].Trim() == ids[2].Trim())
|
|
{
|
|
list.Add(Subtitles[i] + " (" + ids[0].Trim() + ")");
|
|
}
|
|
else
|
|
{
|
|
if (ids.Length >= 1 && types.Length >= 1)
|
|
{
|
|
list.Add(Subtitles[i] + ", " + types[0].Trim() + " (" + ids[0].Trim() + ")");
|
|
}
|
|
if (ids.Length >= 2 && types.Length >= 2)
|
|
{
|
|
list.Add(Subtitles[i] + ", " + types[1].Trim() + " (" + ids[1].Trim() + ")");
|
|
}
|
|
if (ids.Length >= 3 && types.Length >= 3)
|
|
{
|
|
list.Add(Subtitles[i] + ", " + types[2].Trim() + " (" + ids[2].Trim() + ")");
|
|
}
|
|
if (ids.Length >= 4 && types.Length >= 4)
|
|
{
|
|
list.Add(Subtitles[i] + ", " + types[3].Trim() + " (" + ids[3].Trim() + ")");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
public VtsVobs()
|
|
{
|
|
VideoStream = new VideoStream();
|
|
AudioStreams = new List<AudioStream>();
|
|
Subtitles = new List<string>();
|
|
SubtitleIDs = new List<string>();
|
|
SubtitleTypes = new List<string>();
|
|
}
|
|
};
|
|
|
|
public class ProgramChain
|
|
{
|
|
public int NumberOfPgc { get; set; }
|
|
public int NumberOfCells { get; set; }
|
|
public string PlaybackTime { get; set; }
|
|
public List<byte> PgcEntryCells { get; set; }
|
|
public List<string> PgcPlaybackTimes { get; set; }
|
|
public List<string> PgcStartTimes { get; set; }
|
|
public List<char> AudioStreamsAvailable { get; set; }
|
|
public List<byte[]> SubtitlesAvailable { get; set; }
|
|
public List<Color> ColorLookupTable { get; set; }
|
|
|
|
public ProgramChain()
|
|
{
|
|
PgcEntryCells = new List<byte>();
|
|
PgcPlaybackTimes = new List<string>();
|
|
PgcStartTimes = new List<string>();
|
|
AudioStreamsAvailable = new List<char>();
|
|
SubtitlesAvailable = new List<byte[]>();
|
|
ColorLookupTable = new List<Color>();
|
|
}
|
|
|
|
public bool Has43Subs { get; set; }
|
|
public bool HasWideSubs { get; set; }
|
|
public bool HasLetterSubs { get; set; }
|
|
public bool HasPanSubs { get; set; }
|
|
public bool HasNoSpecificSubs { get; set; }
|
|
};
|
|
|
|
public class VtsPgci
|
|
{
|
|
public int NumberOfProgramChains;
|
|
public List<ProgramChain> ProgramChains;
|
|
|
|
public VtsPgci()
|
|
{
|
|
ProgramChains = new List<ProgramChain>();
|
|
}
|
|
};
|
|
|
|
private readonly List<string> _arrayOfAudioMode = new List<string> { "AC3", "...", "MPEG1", "MPEG2", "LPCM", "...", "DTS" };
|
|
private readonly List<string> _arrayOfAudioExtension = new List<string> { "unspecified", "normal", "for visually impaired", "director's comments", "alternate director's comments" };
|
|
private readonly List<string> _arrayOfAspect = new List<string> { "4:3", "...", "...", "16:9" };
|
|
private readonly List<string> _arrayOfStandard = new List<string> { "NTSC", "PAL", "...", "..." };
|
|
private readonly List<string> _arrayOfCodingMode = new List<string> { "MPEG1", "MPEG2" };
|
|
private readonly List<string> _arrayOfNtscResolution = new List<string> { "720x480", "704x480", "352x480", "352x240" };
|
|
private readonly List<string> _arrayOfPalResolution = new List<string> { "720x576", "704x576", "352x576", "352x288" };
|
|
|
|
public VtsPgci VideoTitleSetProgramChainTable => _vtsPgci;
|
|
public VtsVobs VideoTitleSetVobs => _vtsVobs;
|
|
public string ErrorMessage { get; private set; }
|
|
|
|
private readonly VtsVobs _vtsVobs = new VtsVobs();
|
|
private readonly VtsPgci _vtsPgci = new VtsPgci();
|
|
private FileStream _fs;
|
|
|
|
public IfoParser(string fileName)
|
|
{
|
|
try
|
|
{
|
|
_fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
|
|
var buffer = new byte[12];
|
|
_fs.Position = 0;
|
|
_fs.Read(buffer, 0, 12);
|
|
string id = Encoding.UTF8.GetString(buffer);
|
|
if (id != "DVDVIDEO-VTS")
|
|
{
|
|
ErrorMessage = string.Format(Configuration.Settings.Language.DvdSubRip.WrongIfoType, id, Environment.NewLine, fileName);
|
|
return;
|
|
}
|
|
ParseVtsVobs();
|
|
ParseVtsPgci();
|
|
_fs.Close();
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
ErrorMessage = exception.Message + Environment.NewLine + exception.StackTrace;
|
|
}
|
|
}
|
|
|
|
private void ParseVtsVobs()
|
|
{
|
|
var buffer = new byte[16];
|
|
|
|
//retrieve video info
|
|
_fs.Position = 0x200;
|
|
var data = IntToBin(GetEndian(2), 16);
|
|
_vtsVobs.VideoStream.CodingMode = _arrayOfCodingMode[BinToInt(MidStr(data, 0, 2))];
|
|
_vtsVobs.VideoStream.Standard = _arrayOfStandard[BinToInt(MidStr(data, 2, 2))];
|
|
_vtsVobs.VideoStream.Aspect = _arrayOfAspect[BinToInt(MidStr(data, 4, 2))];
|
|
if (_vtsVobs.VideoStream.Standard == "PAL")
|
|
{
|
|
_vtsVobs.VideoStream.Resolution = _arrayOfPalResolution[BinToInt(MidStr(data, 13, 2))];
|
|
}
|
|
else if (_vtsVobs.VideoStream.Standard == "NTSC")
|
|
{
|
|
_vtsVobs.VideoStream.Resolution = _arrayOfNtscResolution[BinToInt(MidStr(data, 13, 2))];
|
|
}
|
|
|
|
//retrieve audio info
|
|
_fs.Position = 0x202; //useless but here for readability
|
|
_vtsVobs.NumberOfAudioStreams = GetEndian(2);
|
|
for (int i = 0; i < _vtsVobs.NumberOfAudioStreams; i++)
|
|
{
|
|
var audioStream = new AudioStream();
|
|
data = IntToBin(GetEndian(2), 16);
|
|
audioStream.LanguageTypeSpecified = Convert.ToInt32(MidStr(data, 4, 2));
|
|
audioStream.CodingMode = _arrayOfAudioMode[(BinToInt(MidStr(data, 0, 3)))];
|
|
audioStream.Channels = BinToInt(MidStr(data, 13, 3)) + 1;
|
|
_fs.Read(buffer, 0, 2);
|
|
audioStream.LanguageCode = new string(new[] { Convert.ToChar(buffer[0]), Convert.ToChar(buffer[1]) });
|
|
var language = DvdSubtitleLanguage.GetLanguageOrNull(audioStream.LanguageCode);
|
|
if (language != null)
|
|
{
|
|
audioStream.Language = language.NativeName;
|
|
}
|
|
|
|
_fs.Seek(1, SeekOrigin.Current);
|
|
audioStream.Extension = _arrayOfAudioExtension[_fs.ReadByte()];
|
|
_fs.Seek(2, SeekOrigin.Current);
|
|
_vtsVobs.AudioStreams.Add(audioStream);
|
|
}
|
|
|
|
//retrieve subs info (only name)
|
|
_fs.Position = 0x254;
|
|
_vtsVobs.NumberOfSubtitles = GetEndian(2);
|
|
_fs.Position += 2;
|
|
for (int i = 0; i < _vtsVobs.NumberOfSubtitles; i++)
|
|
{
|
|
_fs.Read(buffer, 0, 2);
|
|
var languageTwoLetter = new string(new[] { Convert.ToChar(buffer[0]), Convert.ToChar(buffer[1]) });
|
|
_vtsVobs.Subtitles.Add(DvdSubtitleLanguage.GetNativeLanguageName(languageTwoLetter));
|
|
_fs.Read(buffer, 0, 2); // reserved for language code extension + code extension
|
|
|
|
//switch (buffer[0]) // 4, 8, 10-12 unused
|
|
//{
|
|
// // http://dvd.sourceforge.net/dvdinfo/sprm.html
|
|
// case 1: subtitleFormat = "(caption/normal size char)"; break; //0 = unspecified caption
|
|
// case 2: subtitleFormat = "(caption/large size char)"; break;
|
|
// case 3: subtitleFormat = "(caption for children)"; break;
|
|
// case 5: subtitleFormat = "(closed caption/normal size char)"; break;
|
|
// case 6: subtitleFormat = "(closed caption/large size char)"; break;
|
|
// case 7: subtitleFormat = "(closed caption for children)"; break;
|
|
// case 9: subtitleFormat = "(forced caption)"; break;
|
|
// case 13: subtitleFormat = "(director comments/normal size char)"; break;
|
|
// case 14: subtitleFormat = "(director comments/large size char)"; break;
|
|
// case 15: subtitleFormat = "(director comments for children)"; break;
|
|
//}
|
|
|
|
_fs.Position += 2;
|
|
}
|
|
}
|
|
|
|
private static int BinToInt(string p)
|
|
{
|
|
return Convert.ToInt32(p, 2);
|
|
}
|
|
|
|
private static string MidStr(string data, int start, int count)
|
|
{
|
|
return data.Substring(start, count);
|
|
}
|
|
|
|
private static string IntToBin(int value, int digits)
|
|
{
|
|
string result = Convert.ToString(value, 2);
|
|
while (result.Length < digits)
|
|
{
|
|
result = "0" + result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private int GetEndian(int count)
|
|
{
|
|
int result = 0;
|
|
for (int i = count; i > 0; i--)
|
|
{
|
|
int b = _fs.ReadByte();
|
|
result = (result << 8) + b;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void ParseVtsPgci()
|
|
{
|
|
const int sectorSize = 2048;
|
|
|
|
_fs.Position = 0xCC; //Get VTS_PGCI adress
|
|
int tableStart = sectorSize * GetEndian(4);
|
|
|
|
_fs.Position = tableStart;
|
|
_vtsPgci.NumberOfProgramChains = GetEndian(2);
|
|
_vtsPgci.ProgramChains = new List<ProgramChain>();
|
|
|
|
for (int i = 0; i < _vtsPgci.NumberOfProgramChains; i++)
|
|
{
|
|
//Parse PGC Header
|
|
var programChain = new ProgramChain();
|
|
_fs.Position = tableStart + 4 + 8 * (i + 1); //Get PGC adress
|
|
int programChainAdress = GetEndian(4);
|
|
_fs.Position = tableStart + programChainAdress + 2; //Move to PGC
|
|
programChain.NumberOfPgc = _fs.ReadByte();
|
|
programChain.NumberOfCells = _fs.ReadByte();
|
|
programChain.PlaybackTime = InterpretTime(GetEndian(4));
|
|
_fs.Seek(4, SeekOrigin.Current);
|
|
|
|
// check if audio streams are available for this PGC
|
|
_fs.Position = tableStart + programChainAdress + 0xC;
|
|
for (int j = 0; j < _vtsVobs.NumberOfAudioStreams; j++)
|
|
{
|
|
string temp = IntToBin(_fs.ReadByte(), 8);
|
|
programChain.AudioStreamsAvailable.Add(temp[0]);
|
|
_fs.Seek(1, SeekOrigin.Current);
|
|
}
|
|
|
|
// check if subtitles are available for this PGC
|
|
_fs.Position = tableStart + programChainAdress + 0x1C;
|
|
for (int j = 0; j < _vtsVobs.NumberOfSubtitles; j++)
|
|
{
|
|
// read and save full subpicture stream info inside program chain
|
|
var subtitle = new byte[4];
|
|
_fs.Read(subtitle, 0, 4);
|
|
programChain.SubtitlesAvailable.Add(subtitle);
|
|
}
|
|
|
|
CalculateSubtitleTypes(programChain);
|
|
|
|
//Parse Color LookUp Table (CLUT) - offset 00A4, 16*4 (0, Y, Cr, Cb)
|
|
_fs.Position = tableStart + programChainAdress + 0xA4;
|
|
for (int colorNumber = 0; colorNumber < 16; colorNumber++)
|
|
{
|
|
var colors = new byte[4];
|
|
_fs.Read(colors, 0, 4);
|
|
int y = colors[1] - 16;
|
|
int cr = colors[2] - 128;
|
|
int cb = colors[3] - 128;
|
|
int r = (int)Math.Min(Math.Max(Math.Round(1.1644F * y + 1.596F * cr), 0), 255);
|
|
int g = (int)Math.Min(Math.Max(Math.Round(1.1644F * y - 0.813F * cr - 0.391F * cb), 0), 255);
|
|
int b = (int)Math.Min(Math.Max(Math.Round(1.1644F * y + 2.018F * cb), 0), 255);
|
|
|
|
programChain.ColorLookupTable.Add(Color.FromArgb(r, g, b));
|
|
}
|
|
|
|
//Parse Program Map
|
|
_fs.Position = tableStart + programChainAdress + 0xE6;
|
|
_fs.Position = tableStart + programChainAdress + GetEndian(2);
|
|
for (int j = 0; j < programChain.NumberOfPgc; j++)
|
|
{
|
|
programChain.PgcEntryCells.Add((byte)_fs.ReadByte());
|
|
}
|
|
|
|
// Cell Playback Info Table to retrieve duration
|
|
_fs.Position = tableStart + programChainAdress + 0xE8;
|
|
_fs.Position = tableStart + programChainAdress + GetEndian(2);
|
|
var timeArray = new List<int>();
|
|
for (int k = 0; k < programChain.NumberOfPgc; k++)
|
|
{
|
|
int time = 0;
|
|
int max;
|
|
if (k == programChain.NumberOfPgc - 1)
|
|
{
|
|
max = programChain.NumberOfCells;
|
|
}
|
|
else
|
|
{
|
|
max = programChain.PgcEntryCells[k + 1] - 1;
|
|
}
|
|
|
|
for (int j = programChain.PgcEntryCells[k]; j <= max; j++)
|
|
{
|
|
_fs.Seek(4, SeekOrigin.Current);
|
|
time += TimeToMs(GetEndian(4));
|
|
_fs.Seek(16, SeekOrigin.Current);
|
|
}
|
|
programChain.PgcPlaybackTimes.Add(MsToTime(time));
|
|
timeArray.Add(time);
|
|
|
|
//convert to start time
|
|
time = 0;
|
|
for (int l = 1; l <= k; l++)
|
|
{
|
|
time += timeArray[l - 1];
|
|
}
|
|
if (k == 0)
|
|
{
|
|
programChain.PgcStartTimes.Add(MsToTime(0));
|
|
}
|
|
|
|
if (k > 0)
|
|
{
|
|
programChain.PgcStartTimes.Add(MsToTime(time));
|
|
}
|
|
}
|
|
_vtsPgci.ProgramChains.Add(programChain);
|
|
}
|
|
}
|
|
|
|
private void CalculateSubtitleTypes(ProgramChain programChain)
|
|
{
|
|
// Additional Code to analyse stream bytes
|
|
if (_vtsVobs.NumberOfSubtitles > 0)
|
|
{
|
|
// load the 'last' subpicture stream info,
|
|
// because if we have more than one subtitle stream,
|
|
// all subtitle positions > 0
|
|
// lastSubtitle[0] is related to 4:3
|
|
// lastSubtitle[1] is related to Wide
|
|
// lastSubtitle[2] is related to letterboxed
|
|
// lastSubtitle[3] is related to pan&scan
|
|
byte[] lastSubtitle = programChain.SubtitlesAvailable[programChain.SubtitlesAvailable.Count - 1];
|
|
|
|
int countSubs = 0;
|
|
|
|
// set defaults for all possible subpicture types and positions
|
|
programChain.Has43Subs = false;
|
|
programChain.HasWideSubs = false;
|
|
programChain.HasLetterSubs = false;
|
|
programChain.HasPanSubs = false;
|
|
programChain.HasNoSpecificSubs = true;
|
|
|
|
int pos43Subs = -1;
|
|
int posWideSubs = -1;
|
|
int posLetterSubs = -1;
|
|
int posPanSubs = -1;
|
|
|
|
// parse different subtitle bytes
|
|
if (lastSubtitle[0] > 0x80)
|
|
{
|
|
programChain.Has43Subs = true;
|
|
countSubs++; // 4:3
|
|
}
|
|
if (lastSubtitle[1] > 0)
|
|
{
|
|
programChain.HasWideSubs = true;
|
|
countSubs++; // wide
|
|
}
|
|
if (lastSubtitle[2] > 0)
|
|
{
|
|
programChain.HasLetterSubs = true;
|
|
countSubs++; // letterboxed
|
|
}
|
|
if (lastSubtitle[3] > 0)
|
|
{
|
|
programChain.HasPanSubs = true;
|
|
countSubs++; // pan&scan
|
|
}
|
|
|
|
if (countSubs == 0)
|
|
{
|
|
// may be, only a 4:3 stream exists
|
|
// -> lastSubtitle[0] = 0x80
|
|
}
|
|
else
|
|
{
|
|
if (_vtsVobs.NumberOfSubtitles == 1)
|
|
{
|
|
// only 1 stream exists, may be letterboxed
|
|
// if so we cound't find wide id, because lastSubtitle[1] = 0 !!
|
|
// corresponding wide stream byte is 0 => wide id = 0x20
|
|
// letterboxed = 0x21
|
|
if (programChain.HasLetterSubs && !programChain.HasWideSubs)
|
|
{
|
|
// repair it
|
|
programChain.HasWideSubs = true;
|
|
}
|
|
}
|
|
programChain.HasNoSpecificSubs = false;
|
|
}
|
|
|
|
// subpucture streams start with 0x20
|
|
int subStream = 0x20;
|
|
|
|
// Now we know all about available subpicture streams, including position type
|
|
// And we can create whole complete definitions for all avalable streams
|
|
foreach (byte[] subtitle in programChain.SubtitlesAvailable)
|
|
{
|
|
if (programChain.HasNoSpecificSubs)
|
|
{
|
|
// only one unspezified subpicture stream exists
|
|
_vtsVobs.SubtitleIDs.Add($"0x{subStream++:x2}");
|
|
_vtsVobs.SubtitleTypes.Add("unspecific");
|
|
}
|
|
else
|
|
{
|
|
// read stream position for evey subtitle type from subtitle byte
|
|
if (programChain.Has43Subs)
|
|
{
|
|
pos43Subs = subtitle[0] - 0x80;
|
|
}
|
|
if (programChain.HasWideSubs)
|
|
{
|
|
posWideSubs = subtitle[1];
|
|
}
|
|
if (programChain.HasLetterSubs)
|
|
{
|
|
posLetterSubs = subtitle[2];
|
|
}
|
|
if (programChain.HasPanSubs)
|
|
{
|
|
posPanSubs = subtitle[3];
|
|
}
|
|
|
|
// Now we can create subpicture id's and types for every stream
|
|
// All used subpicture id's and types will beappended to string, separated by colon
|
|
// So it's possible to split it later
|
|
string sub = string.Empty;
|
|
string subType = string.Empty;
|
|
if (programChain.Has43Subs)
|
|
{
|
|
sub = $"0x{subStream + pos43Subs:x2}";
|
|
subType = "4:3";
|
|
}
|
|
if (programChain.HasWideSubs)
|
|
{
|
|
if (sub.Length > 0)
|
|
{
|
|
sub += ", ";
|
|
subType += ", ";
|
|
}
|
|
sub += $"0x{subStream + posWideSubs:x2}";
|
|
subType += "wide";
|
|
}
|
|
if (programChain.HasLetterSubs)
|
|
{
|
|
if (sub.Length > 0)
|
|
{
|
|
sub += ", ";
|
|
subType += ", ";
|
|
}
|
|
sub += $"0x{subStream + posLetterSubs:x2}";
|
|
subType += "letterboxed";
|
|
}
|
|
if (programChain.HasPanSubs)
|
|
{
|
|
if (sub.Length > 0)
|
|
{
|
|
sub += ", ";
|
|
subType += ", ";
|
|
}
|
|
sub += $"0x{subStream + posPanSubs:x2}";
|
|
subType += "pan&scan";
|
|
}
|
|
|
|
_vtsVobs.SubtitleIDs.Add(sub);
|
|
_vtsVobs.SubtitleTypes.Add(subType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int TimeToMs(int time)
|
|
{
|
|
double fps;
|
|
|
|
var temp = IntToBin(time, 32);
|
|
var result = StrToInt(IntToHex(BinToInt(MidStr(temp, 0, 8)), 1)) * 3600000;
|
|
result = result + StrToInt(IntToHex(BinToInt(MidStr(temp, 8, 8)), 2)) * 60000;
|
|
result = result + StrToInt(IntToHex(BinToInt(MidStr(temp, 16, 8)), 2)) * 1000;
|
|
if (temp.Substring(24, 2) == "11")
|
|
{
|
|
fps = 30;
|
|
}
|
|
else
|
|
{
|
|
fps = 25;
|
|
}
|
|
|
|
result += (int)Math.Round((TimeCode.BaseUnit / fps) * StrToFloat(IntToHex(BinToInt(MidStr(temp, 26, 6)), 3)));
|
|
return result;
|
|
}
|
|
|
|
private static double StrToFloat(string p)
|
|
{
|
|
return Convert.ToDouble(p, System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static int StrToInt(string p)
|
|
{
|
|
return int.Parse(p);
|
|
}
|
|
|
|
private static string IntToHex(int value, int digits)
|
|
{
|
|
string hex = value.ToString("X");
|
|
|
|
return hex.PadLeft(digits, '0');
|
|
}
|
|
|
|
private static string MsToTime(double milliseconds)
|
|
{
|
|
var ts = TimeSpan.FromMilliseconds(milliseconds);
|
|
string s = $"{ts.Hours:#0}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
|
|
return s;
|
|
}
|
|
|
|
private static string InterpretTime(int timeNumber)
|
|
{
|
|
string timeBytes = IntToBin(timeNumber, 32);
|
|
int h = StrToInt(IntToHex(BinToInt(timeBytes.Substring(0, 8)), 1));
|
|
int m = StrToInt(IntToHex(BinToInt(timeBytes.Substring(8, 8)), 2));
|
|
int s = StrToInt(IntToHex(BinToInt(timeBytes.Substring(16, 8)), 2));
|
|
int fps = 25;
|
|
if (timeBytes.Substring(24, 2) == "11")
|
|
{
|
|
fps = 30;
|
|
}
|
|
|
|
int milliseconds = (int)Math.Round((TimeCode.BaseUnit / fps) * StrToFloat(IntToHex(BinToInt(timeBytes.Substring(26, 6)), 3)));
|
|
var ts = new TimeSpan(0, h, m, s, milliseconds);
|
|
return MsToTime(ts.TotalMilliseconds);
|
|
}
|
|
|
|
private void ReleaseManagedResources()
|
|
{
|
|
if (_fs != null)
|
|
{
|
|
_fs.Dispose();
|
|
_fs = null;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
ReleaseManagedResources();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|