)8r&*L3PA
zGNYd+_^;nNKmTI~dY_~zXvYtC&`V1XP`{HN)&XeWGrj5UlH
zZee!1VNvH+5`n^X)VT_uHU@y|7KTyy(Z?5qDg$KFEcW)=_8v;X0rL`a)!hGn@n}0a
z!pymqFnJDNAMzAKwQ-_dH6PqoTW*@0bIGbgg_4V*9x0{sQ*CWmB{
zCeF05wqYfu%aUy;K=dliZ4bLVR6F%cgcF4tU#>fXa5Jho-GFog`9%anGa*?tt#`gv51zbJFbZpG+=eKbo&brl>piU(=FUrL*v%^1KT3SwBH<`1;$<}htc5GWLgY8n5X8dRx6S|dRbF$(&MAHwb{23GZb^tG;PV4|Q)OA)CH&$x5x
z^6A@po{orr<=f}~?N`B}df>Ti>g_7WArV5DI`Trc&QEbgq*`4VnY)=@(#yaHX^V!r
zbo+-j2rVb$%#S2pGbKPbZ)8D`hqBPEdT+)>YFSB!-rhmAHjS!yyDInZtEXnXt^9c&
zU)dIgeaii;y+7{#f=8PD`~G#$CK0;|*4f0gy~S5`iK?oCG{r%CriEw#NZIGH=7QR8
zwmXwIhA$ili;#J>xLoOKaP)Xx0ZSNmFV68J@XqI6eE>l8x?q>`Q1BaA`9;IOAqO)Y
z`-g#*1AQlkop833=9a0X_WK<)qZ$4u4Kb|@w!~L`Kl_u!og!QcNMFlRtpK%V#zEKd
z9`F?8_RCfii3E^sbSIuY$PbLg3^0qTd6?^*Bz<#w{WGw(NzIxBoM71GHc$F_CCDK|
zD#O*CdpRM40kPB2W;pi&D?_|p-{Y}rB&9furJCJ_BzE`$JErD%1mj7dvytXv}NCML!)g&Fjnqns32!WA~{ZIEY3HLstQ&@55lm4W9r
zQF(g9g`z+N>UVvVsPLmnVA?8(2_dO
z@L(I;NA&Ec9)Nbkw?FG>EBUA2@XihhLMM6rL>=%s8qRV|uI%V6lvM?QfYjKZNS|Ne
z@78l?1G?&t4{^)ENK2xK9-^CxwB?9mAkdc%%1EHnd8G1N0Rx7HNbkuA0ba0V=rQ|
zVt>}Ta!mPuQ*+dDGRjJrDviGR48=Gk`%;Ca(=GQWony#EsRSr4;h~H`q$FGGaq`mN
z0DwZyyop0a|BGgrb+rK1qj#R9I2IGM%G8Jw<__NI>4HDVRC~a2h=Fb1ySb^RuQnM?
zJ4NxA<&_KVpg$Q>{)&?0%8FaV9hs%j;5pnz`j@|
zF+1rzyYz;z)9R%&42Qrj!I$v5PVFLfoL_LiQNw592kSC){6wLA+e`Y)Kxg84j{{>^
z@Z>0DC{e%GEP30%6e1XUbqvGae@NgNFM#IANo)k9cIRnP-}2$Yx*)!`Vk9l^-8)cP
zbY|#7f5HzI1};cY`6te0UR6Bp21_DRC34xzXpi|
zd63C~WVSvzthcq09*E%CyU6+|`bK3=%OTX>jFE2Cv4prn?R7&_dm--vM@dpBJi_mq
zff)M+PtK_~`vdAUqM7?cEAYVSESgJG*9K!B(QuN(-rmuxDofgKzfwBSp#PE|i(
zB;r7|D`RL7DZAc1t6Ur|vjKR`Jfw?^hWMopf~-gQb_x5M1Bd`AN?j}mkil!@f6Cn4
zo^(kIJJT5f8>jS-@>C~RTUjq%Edd5k=8o24R>e6rvl}U*Gj1E}%(c#9YC)K2;)Trs
zq1nhdW{%?n(#N^m<~mF`kvf}pW`$g*_-vMub%IZmE_Zz`Z~LzAp|PFSF46Q0+L?eU
zR9$s;^ePI#%Eh~$YwrMF2%E8g_lj`$RA*aOdZ6y51jvXoae&
z&i=gm4N$PP190>KO=ySBI<3uU@~?j8aF6-P@(kdyNFtz0JJ
zQ@%~Rl~vh9BsPFaVx^8aBBs&?T1d$$O78P%*=>GKEscN{@TSS`R^UVF%CXENN5$-)
ztYD0$Pz_a8b&dZ5YZd5+V28!WW!rA%fHsPh1k_cXku)*^;*L@u-|&T!^q3D{4?EHe
zK`Q&3!#AC|mIlEvzH}U8#w_p{EireN{skqK?Z3u}3^X8oCBHD&YyjY6pz}z`5pH38
z`fCpsLtbJ}KJQ(l_yt&!v^NYa&BvR5M7Iuzzhwf7T7=3*5kb
zYI}2TTe$^U(|^zAgOd$Qz*lFSpXRFHRhyk~u1me9r}ovMMw}xFwJIB1r0*WvO&A`}
zlHG08^aKdiACDQy$Ay}7Wi^YD^o>GJ5t>qLbrr|i?!K1$pVTQGzy4q+1TaO$(ovhX
zR8#g&(wwt{iDdO;u5r~Cj@DMeADcP(r=~LxypIWCH|VxDptC+>)vkwnk6*{CpZt;0
z>r>E773uH3AwL-RMhguH@ss@v=!CraOe|&YSELx2p1#dk!*`?kM9}#ADJp8)Nczyk
zo(~iWwwo=&)!^Z6(ZmY0I_r3P3o@MB1R7AEOT$*{j?0&=1^AhK7%)(@!weecbiwV0
z9;_g7!s+uKnhsNoXWW$nJOOZie~j}
zpDKFj?*H11EuU9S5M7kLFt`JxBTCtip}2mW%?=k_?(|to{=WMcTyTFwcpzi
zJ-Kqr`U#W%OgLK0)o;83=hbeGs9^Tdp~>1{!aOp^N5O+YQmyR3bY7N3bVtM0PXol)wjH7(C45kZYIsHj3}AV89^$9?~Ifmv%UpAd}6EvRuc@8pyq
zOpUe-bn@WX#DNrWFv6`pYgl{5C!dEw>`l`c#OC0pctZzPs8sN^vu7>n%E@{IxCX(>
zT*4$DiDgPB(Qe$DsJlf5?x0Y@eDL83eoHvn6-pp+?GzwIK+4XMnd*D
zq~MsiUX(y%+nC=i8p3g#RJAwQUK#S>>Wn+e=r}(li)P~i!*njngB8oys<#G9yj{xx
zU`wzWHOydZ>`Y)%A0o4m1$K%_Az$=B{O~txVgyUdM^*tZz+KfeXe9L1A$VU=5U5IK_-l
zR#^ENCit=Le$l?XGlXLyF!`@f_hq)xG|
zZmmcZwOSRb*P`|+P2G@d(W+<>l}&Ml^Z!B;4EEBNUeMeBJ?D8QbI#29eeXH%gvkVk
zZvX)rLtX^X#!ww#G}R*`6>TdwAd$97B+9lC@VyTp6e`*~wSk8-fac8=?Enh3F$7rc
z=?xn?c%*!9RSzv%jSJp61<)}VHh93Og}D!Y_FU^TrhR6~g$Lhs9%(XT
z+0CV^Y^^qoT)TO4?q|AsI!o4!GO{`Oo1*?
z*hGzii-$sJD~Qb0ghoqfyFu8aVH-x{U{-PB%Yrjqu59l!k;I;kF)=m{2;KkBA2tjzI#kiZYN$+*b8ud0?%}yp
zeGlAg7rx~F>eZ`5N^chyWG^}Fe7C6L>6f0ldA-Xl;Wow}rRJ51;*)WC52ntTIn%nm
zi*wH%VhurZ_b6S7^KqA8|4w@*W*nE@J$JX+1gDbRgh9)C?SH;lzF#M^m#51X*CC!M
z@2oL8P;OG1m-+DXO=eH$X-LMjGK@4gbKI(L+Bc{Chxp*KJLl48Tv;PF(9w)c4G?2a
zLWR$*Z(ka07}E6M%)H)yn)1zkkkM~}v!1^G!DD(43x7Q=Yn!d3jqx&HL>sOYYZ!F(B94l6=xFC)7o5k)wnOE^V>8BNUL(MnHcP&Ke*TB+N6*i+bf3ARtB=M_gZPN)*4VM6+%@;$tkC?d!}AJ9J1#8F{QCNyqO%?P2Hm_Z
zJ{jGwljwfV<$qW{{S;nNt{Ok?UBIp;ZY%6;zM`2yQNm)IXT=}Gd@ALLNGQOLg@ku$mamm%eEkQGKhTNzRq5bYIGzSyd`l_L${;5gnx`xiW>hcfqFv;VZ29`b7>x<{3@0Br9@Ytaa2ykeo!)aUV-cbtRttrAH3v
zz1xKbeab`g?2+d`*iygC7GexRHBc`lhnf3CGSxNiJxkV$_QblYBO57TsJfR2Xva3*q>2ZM|ChF
zaC`BC_eis&;PmeEx?646H(MK>xb^Y3^Ub3BFD&-T9yZKu;A7l_N4q`R->eL@lUQT
zvz+Z}L2+cbJ-(!*@M#(
zStD>vqILQQC(HJKcKC|Hv+?ZO;fW4M%9Cl|O^uj*ok2sQ*KV6vc=?ja(*BGU|1xNu
z$X-6D)3_ElC@uU&(`gs(oQvDodW-GE%Ft(}=66Euyff&CSUKWC{+iXLX5XESTfZY^
zqOW72@b>tZXU?1np|kU}u&|@6UNoa0xA%GozDY9u_`@GIo_oqF#tqOu5-^|%cDgqB
zhsm4z(E4xHNz2tJE*X-PUa>zYOZj0JkC3hSaZN+KHXhK>7Y7QT@3Z^<6UA{czGLYM
z3B%s;7@RtA(o!p`N6I(Q<&*sSthQp*))?)D=IGeD6S{Qk3Ts;%^yq1aUVZw&p!PC(w=oWz
z;^VPp>sBNtB_Sy}8Ofi2j%_I^*tL5%_N4BCbl*N4Jah=(96pR=$BrQ*GXvk9IDu2A
zP9f`b7GyFRa{rZ!>o;!T=FOYP&(B9eK>_aHzmJmAQapbA7|)+SM`dND!WNM};AIR1
zS^zQDK$<;}Edh#$!;p=I9SedtTZtf+fLOKJvF|)&1=8Z)ZC!WRP0F&Y(lU4Jrh_~
z5}!(ZChB%CqUoOm*iVLFId)(_dxPB$0=pLrRxAZ8msO8n)QWhrF0cpj&cwSD
zPqB)p5x>X~EXocn!5eIE5ZHII6jKV8FMGu|B3_ety2g*uA)c=Ovzrl5hlL4SfH~WN
zjq?Uu90ay07VMxD?4s-y4}Ex{4Fb^tG3bso*drSf6k`~ep)Z(SAei?Gu%NAAvHQTJ
zr@>^ej-LoZ;_ZodBYraR3yF^=ejD-oi9b$!7V)GRdyV*f;)|>Gts%k)iZG2LtfUAj
z6yYBf;TMWfY{*b*$MDFTp*)D;Ni4%NDZ>jHtLEDgZ$rEb@gs@%BYr;dtBBuh$gtgx
zA>EtdL=Z!EEWiWSR{A!W#w
zz2a*-H}LJ;0qy{IfIGk);12x1cfhl*rJfzBtGp-IuU7}S|8;brUfl=ksP0+c@Bez9
z4iCp2;0|yHxC7h)?f`dyJHQ>_4sZv!1Ka`b0C#{pz#ZTYa0j>p+yU+YcYr&<9pDaB
zIiO?Q+16InwLRVc_~whi)V}&Jk^Ri+_F?s++@rp!p6ku~RjbP@eQSEq;QE#|)T!ZO
zsy@Mn*6;SZJhe^K2GGFz!ha+0-29E{>pH*EcXCqJ-=P0JRq~FebW3vM_KwuQ&i8)vVvOl`^^$-HWX(?UW-?wHs7l^^@ODrS{u(
zL+WcOgZF>!oTomDhSYDV3{H2^*IF7bH5z`iB3b`esW1+rFjw
z8!7cWy(N8prGB5cqz|!T0M51lx0dQ2vELctf3QoWG@yI!F_b-O@9>}OX{C(r+(H$l
zk)EoiTsNq`K=lD6ZmE!K>L8|nM^Y~hst;pjcu&ust#x%<*tpR3Z_N+jhSf)}>H$<&
zdRKiKRyS)_)UBzHdSlfQP?PB<53JT!uhxfVjTkzrtIk&{;wEoUA6k80DOc5;s=ffd
zwo%orG4}dt2s$cHLkbOBMT!rV;A`vpk$f$RriJPYsMtpJ?e8^RRr58n-kMr1O)P{a
z2ETJ5SZ`815O;t(z#ZTYa0j>p+yU+YcYr&<9pDad2e<>=fd+O!o+nH~OC&9Slqbxj
zy(vu1&0ucP0m6?_<*B$?)^{hyN5szOaDl(-9OLjL@(!e6(z7bzZPlt5+j>{d#OoNT|vK
zmb!N@<^4K{qeqWY&acd>oL^a4S;)D1wJO)w4a)a*=guA6yLS(zl;`Ww<43RM`RYK4
zrW`2I6eXvP@uNgjizw05dP+34o$`F8Q=YHml;`U-<@vfmdA_bup09k$pYkb;fwQi0
zLsQ*&SW^EQ_k-7wZ$O?ojskG(m8VBVM8chrgDA6`A+yU+Yci>MtApbqFkQNCofAo9es^=o;djYKi0h1U4
z(*R3)Ac1u;48;s~V;HpNTJ2~nrf}uu3D?M^R?Uo`E^QjT7lFU?Z2sIU{@km-^xUhreR?H*`5CObR8@bk
NP5rBwYNA0a{|AgCT=oC}
literal 0
HcmV?d00001
diff --git a/ChapterTool.Avalonia/ChapterTool.Avalonia.csproj b/ChapterTool.Avalonia/ChapterTool.Avalonia.csproj
new file mode 100644
index 0000000..8137a05
--- /dev/null
+++ b/ChapterTool.Avalonia/ChapterTool.Avalonia.csproj
@@ -0,0 +1,31 @@
+
+
+ WinExe
+ net8.0
+ enable
+ true
+ app.manifest
+ true
+ ChapterTool
+ ChapterTool
+ Assets\icon.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
diff --git a/ChapterTool.Avalonia/Program.cs b/ChapterTool.Avalonia/Program.cs
new file mode 100644
index 0000000..4accb50
--- /dev/null
+++ b/ChapterTool.Avalonia/Program.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using System;
+
+namespace ChapterTool.Avalonia;
+
+sealed class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+}
diff --git a/ChapterTool.Avalonia/ViewLocator.cs b/ChapterTool.Avalonia/ViewLocator.cs
new file mode 100644
index 0000000..1cef4f0
--- /dev/null
+++ b/ChapterTool.Avalonia/ViewLocator.cs
@@ -0,0 +1,31 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using ChapterTool.Avalonia.ViewModels;
+
+namespace ChapterTool.Avalonia;
+
+public class ViewLocator : IDataTemplate
+{
+
+ public Control? Build(object? param)
+ {
+ if (param is null)
+ return null;
+
+ var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ public bool Match(object? data)
+ {
+ return data is ViewModelBase;
+ }
+}
diff --git a/ChapterTool.Avalonia/ViewModels/MainWindowViewModel.cs b/ChapterTool.Avalonia/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..4b85ce3
--- /dev/null
+++ b/ChapterTool.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,6 @@
+namespace ChapterTool.Avalonia.ViewModels;
+
+public partial class MainWindowViewModel : ViewModelBase
+{
+ public string Greeting { get; } = "Welcome to Avalonia!";
+}
diff --git a/ChapterTool.Avalonia/ViewModels/ViewModelBase.cs b/ChapterTool.Avalonia/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..26acbc3
--- /dev/null
+++ b/ChapterTool.Avalonia/ViewModels/ViewModelBase.cs
@@ -0,0 +1,7 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace ChapterTool.Avalonia.ViewModels;
+
+public class ViewModelBase : ObservableObject
+{
+}
diff --git a/ChapterTool.Avalonia/Views/MainWindow.axaml b/ChapterTool.Avalonia/Views/MainWindow.axaml
new file mode 100644
index 0000000..11fbd97
--- /dev/null
+++ b/ChapterTool.Avalonia/Views/MainWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ChapterTool.Avalonia/Views/MainWindow.axaml.cs b/ChapterTool.Avalonia/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..d4281c6
--- /dev/null
+++ b/ChapterTool.Avalonia/Views/MainWindow.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace ChapterTool.Avalonia.Views;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/ChapterTool.Avalonia/app.manifest b/ChapterTool.Avalonia/app.manifest
new file mode 100644
index 0000000..715420e
--- /dev/null
+++ b/ChapterTool.Avalonia/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChapterTool.Core/ChapterData/IData.cs b/ChapterTool.Core/ChapterData/IData.cs
new file mode 100644
index 0000000..6be90e3
--- /dev/null
+++ b/ChapterTool.Core/ChapterData/IData.cs
@@ -0,0 +1,15 @@
+namespace ChapterTool.ChapterData
+{
+ using ChapterTool.Util;
+
+ public interface IData// : IEnumerable
+ {
+ int Count { get; }
+
+ ChapterInfo this[int index] { get; }
+
+ string ChapterType { get; }
+
+ // event Action OnLog;
+ }
+}
diff --git a/ChapterTool.Core/ChapterTool.Core.csproj b/ChapterTool.Core/ChapterTool.Core.csproj
new file mode 100644
index 0000000..1c34597
--- /dev/null
+++ b/ChapterTool.Core/ChapterTool.Core.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+ ChapterTool
+
+
+
+
+
+
+
diff --git a/ChapterTool.Core/Knuckleball/Chapter.cs b/ChapterTool.Core/Knuckleball/Chapter.cs
new file mode 100644
index 0000000..2146930
--- /dev/null
+++ b/ChapterTool.Core/Knuckleball/Chapter.cs
@@ -0,0 +1,111 @@
+// -----------------------------------------------------------------------
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+// Portions created by Jim Evans are Copyright © 2012.
+// All Rights Reserved.
+//
+// Contributors:
+// Jim Evans, james.h.evans.jr@@gmail.com
+//
+//
+// -----------------------------------------------------------------------
+namespace Knuckleball
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// Represents a chapter in an MP4 file.
+ ///
+ public class Chapter
+ {
+ private string _title = string.Empty;
+ private TimeSpan _duration = TimeSpan.FromSeconds(0);
+
+ ///
+ /// Occurs when the value of any property is changed.
+ ///
+ internal event EventHandler Changed;
+
+ ///
+ /// Gets or sets the title of this chapter.
+ ///
+ public string Title
+ {
+ get => _title;
+
+ set
+ {
+ if (_title != value)
+ {
+ _title = value;
+ OnChanged(new EventArgs());
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the duration of this chapter.
+ ///
+ public TimeSpan Duration
+ {
+ get => _duration;
+
+ set
+ {
+ if (_duration != value)
+ {
+ _duration = value;
+ OnChanged(new EventArgs());
+ }
+ }
+ }
+
+ ///
+ /// Returns the string representation of this chapter.
+ ///
+ /// The string representation of the chapter.
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0} ({1} milliseconds)", Title, Duration.TotalMilliseconds);
+ }
+
+ ///
+ /// Returns the hash code for this .
+ ///
+ /// A 32-bit signed integer hash code.
+ public override int GetHashCode()
+ {
+ return ToString().GetHashCode();
+ }
+
+ ///
+ /// Determines whether two objects have the same value.
+ ///
+ /// Determines whether this instance and a specified object, which
+ /// must also be a object, have the same value.
+ /// if the object is a and its value
+ /// is the same as this instance; otherwise, .
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Chapter other))
+ {
+ return false;
+ }
+
+ return Title == other.Title && Duration == other.Duration;
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// An that contains the event data.
+ protected void OnChanged(EventArgs e)
+ {
+ Changed?.Invoke(this, e);
+ }
+ }
+}
diff --git a/ChapterTool.Core/Knuckleball/IntPtrExtensions.cs b/ChapterTool.Core/Knuckleball/IntPtrExtensions.cs
new file mode 100644
index 0000000..517be7b
--- /dev/null
+++ b/ChapterTool.Core/Knuckleball/IntPtrExtensions.cs
@@ -0,0 +1,206 @@
+// -----------------------------------------------------------------------
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+// Portions created by Jim Evans are Copyright © 2012.
+// All Rights Reserved.
+//
+// Contributors:
+// Jim Evans, james.h.evans.jr@@gmail.com
+//
+//
+// -----------------------------------------------------------------------
+namespace Knuckleball
+{
+ using System;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// The class contains extension methods used
+ /// for marshaling data between managed and unmanaged code.
+ ///
+ public static class IntPtrExtensions
+ {
+ ///
+ /// Reads a 32-bit integer value beginning at the location pointed to
+ /// in memory by the specified pointer value.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The 32-bit integer value pointed to by this . Returns
+ /// if this pointer is a null pointer ().
+ public static int? ReadInt(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return Marshal.ReadInt32(value);
+ }
+
+ ///
+ /// Reads a 64-bit integer value beginning at the location pointed to
+ /// in memory by the specified pointer value.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The 64-bit integer value pointed to by this . Returns
+ /// if this pointer is a null pointer ().
+ public static long? ReadLong(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return Marshal.ReadInt64(value);
+ }
+
+ ///
+ /// Reads a 16-bit integer value beginning at the location pointed to
+ /// in memory by the specified pointer value.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The 16-bit integer value pointed to by this . Returns
+ /// if this pointer is a null pointer ().
+ public static short? ReadShort(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return Marshal.ReadInt16(value);
+ }
+
+ ///
+ /// Reads an 8-bit integer value beginning at the location pointed to
+ /// in memory by the specified pointer value.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The 8-bit integer value pointed to by this . Returns
+ /// if this pointer is a null pointer ().
+ public static byte? ReadByte(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return Marshal.ReadByte(value);
+ }
+
+ ///
+ /// Reads an 8-bit integer value beginning at the location pointed to
+ /// in memory by the specified pointer value, and coerces that value into
+ /// a boolean.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// if the value pointed to by this
+ /// is non-zero; if the value pointed to is zero.
+ /// Returns if this pointer is a null pointer ().
+ public static bool? ReadBoolean(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ return Marshal.ReadByte(value) != 0;
+ }
+
+ ///
+ /// Reads an enumerated value beginning at the location pointed to in
+ /// memory by the specified pointer value.
+ ///
+ /// A value derived from .
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The default value of the enumerated value to return
+ /// if the memory location pointed to by this is a null pointer
+ /// ().
+ /// The enumerated value pointed to by this . Returns
+ /// the specified default value if this pointer is a null pointer ().
+ public static T ReadEnumValue(this IntPtr value, T defaultValue)
+ where T : struct
+ {
+ if (value == IntPtr.Zero)
+ {
+ return defaultValue;
+ }
+
+ if (!typeof(T).IsEnum)
+ {
+ throw new ArgumentException("Type T must be an enumerated value");
+ }
+
+ object rawValue;
+ var underlyingType = Enum.GetUnderlyingType(typeof(T));
+ if (underlyingType == typeof(byte))
+ {
+ rawValue = ReadByte(value).Value;
+ }
+ else if (underlyingType == typeof(long))
+ {
+ rawValue = ReadLong(value).Value;
+ }
+ else if (underlyingType == typeof(short))
+ {
+ rawValue = ReadShort(value).Value;
+ }
+ else
+ {
+ rawValue = value.ReadInt().Value;
+ }
+
+ return (T)Enum.ToObject(typeof(T), rawValue);
+ }
+
+ ///
+ /// Reads a structure beginning at the location pointed to in memory by the
+ /// specified pointer value.
+ ///
+ /// The type of the structure to read.
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// An instance of the specified structure type.
+ /// Thrown when this
+ /// is a null pointer ().
+ public static T ReadStructure(this IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ throw new ArgumentNullException(nameof(value), "Structures cannot be read from a null pointer (IntPtr.Zero)");
+ }
+
+ return (T)Marshal.PtrToStructure(value, typeof(T));
+ }
+
+ ///
+ /// Reads a block of memory beginning at the location pointed to by the specified
+ /// pointer value, and copies the contents into a byte array of the specified length.
+ ///
+ /// The value indicating the location
+ /// in memory at which to begin reading data.
+ /// The number of bytes to read into the byte array.
+ /// The byte array containing copies of the values pointed to by this . Returns
+ /// if this pointer is a null pointer ().
+ public static byte[] ReadBuffer(this IntPtr value, int bufferLength)
+ {
+ if (value == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ var buffer = new byte[bufferLength];
+ Marshal.Copy(value, buffer, 0, bufferLength);
+ return buffer;
+ }
+ }
+}
diff --git a/ChapterTool.Core/Knuckleball/MP4File.cs b/ChapterTool.Core/Knuckleball/MP4File.cs
new file mode 100644
index 0000000..e2f8528
--- /dev/null
+++ b/ChapterTool.Core/Knuckleball/MP4File.cs
@@ -0,0 +1,148 @@
+// -----------------------------------------------------------------------
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+// Portions created by Jim Evans are Copyright © 2012.
+// All Rights Reserved.
+//
+// Contributors:
+// Jim Evans, james.h.evans.jr@@gmail.com
+//
+//
+// -----------------------------------------------------------------------
+namespace Knuckleball
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Runtime.InteropServices;
+ using System.Text;
+
+ ///
+ /// Represents an instance of an MP4 file.
+ ///
+ public class MP4File
+ {
+ private readonly string _fileName;
+
+ public static event Action OnLog;
+
+ ///
+ /// Prevents a default instance of the