summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--macosx/English.lproj/Subtitles.xib365
-rw-r--r--macosx/HBJob+HBJobConversion.m135
-rw-r--r--macosx/HBJob+UIAdditions.m15
-rw-r--r--macosx/HBJob.m4
-rw-r--r--macosx/HBSubtitles.h41
-rw-r--r--macosx/HBSubtitles.m509
-rw-r--r--macosx/HBSubtitlesController.m533
-rw-r--r--macosx/HBSubtitlesTrack.h85
-rw-r--r--macosx/HBSubtitlesTrack.m420
-rw-r--r--macosx/HBTitle.m12
-rw-r--r--macosx/HandBrake Tests/HBMockTitle.m11
-rw-r--r--macosx/HandBrake.xcodeproj/project.pbxproj6
12 files changed, 1159 insertions, 977 deletions
diff --git a/macosx/English.lproj/Subtitles.xib b/macosx/English.lproj/Subtitles.xib
index 5d2f05802..bbc7f7275 100644
--- a/macosx/English.lproj/Subtitles.xib
+++ b/macosx/English.lproj/Subtitles.xib
@@ -1,14 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8164.2" systemVersion="15A225f" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment identifier="macosx"/>
<development version="6300" identifier="xcode"/>
- <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8164.2"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9059"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="HBSubtitlesController">
<connections>
- <outlet property="fTableView" destination="0yM-wE-D2x" id="0vq-y5-Ubi"/>
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
</connections>
</customObject>
@@ -18,6 +17,77 @@
<rect key="frame" x="0.0" y="0.0" width="926" height="322"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
+ <popUpButton verticalHuggingPriority="750" id="2Tb-KC-Ugi">
+ <rect key="frame" x="17" y="291" width="88" height="22"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <popUpButtonCell key="cell" type="push" title="Track" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="8ZD-D6-TLA">
+ <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ <menu key="menu" title="OtherViews" id="Fr7-eG-NuL">
+ <items>
+ <menuItem title="Track" state="on" hidden="YES" id="TJO-RZ-jgb"/>
+ <menuItem title="Add All" id="4PX-In-DpF">
+ <connections>
+ <action selector="addAll:" target="-2" id="tuS-uF-dje"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Add External SRT…" toolTip="Add new SRT subtitle to the list." id="HW0-PS-t0U">
+ <connections>
+ <action selector="browseImportSrtFile:" target="-2" id="dog-BP-my4"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="2hO-bG-5qB"/>
+ <menuItem title="Remove All" id="mVi-zH-KUq">
+ <connections>
+ <action selector="removeAll:" target="-2" id="QsA-lb-0rD"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ <connections>
+ <binding destination="-2" name="enabled" keyPath="self.subtitles" id="khN-Sn-dnI">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSIsNotNil</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </popUpButton>
+ <button verticalHuggingPriority="750" id="QsM-28-Pya">
+ <rect key="frame" x="110" y="288" width="140" height="28"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <buttonCell key="cell" type="push" title="Configure Defaults…" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oxg-bs-1si">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <action selector="showSettingsSheet:" target="-2" id="OAA-S8-tfS"/>
+ <binding destination="-2" name="enabled" keyPath="self.subtitles" id="dpe-kM-iMF">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSIsNotNil</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </button>
+ <button verticalHuggingPriority="750" id="Vxx-gk-9kY">
+ <rect key="frame" x="248" y="288" width="116" height="28"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <buttonCell key="cell" type="push" title="Reload Defaults" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jG8-uo-1tv">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <action selector="addTracksFromDefaults:" target="-2" id="GOz-FT-Atg"/>
+ <binding destination="-2" name="enabled" keyPath="self.subtitles" id="CSa-3s-GD6">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSIsNotNil</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </button>
<scrollView autohidesScrollers="YES" horizontalLineScroll="27" horizontalPageScroll="10" verticalLineScroll="27" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="Syo-rH-vof">
<rect key="frame" x="20" y="20" width="886" height="266"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -25,7 +95,7 @@
<rect key="frame" x="1" y="17" width="884" height="248"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
- <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="25" headerView="IiW-3a-Drv" id="0yM-wE-D2x">
+ <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="25" rowSizeStyle="automatic" headerView="IiW-3a-Drv" viewBased="YES" id="0yM-wE-D2x">
<rect key="frame" x="0.0" y="0.0" width="884" height="248"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
@@ -49,6 +119,35 @@
</menu>
</popUpButtonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <prototypeCellViews>
+ <tableCellView id="EZp-rc-7mo">
+ <rect key="frame" x="1" y="1" width="322" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <popUpButton verticalHuggingPriority="750" id="Tf3-cP-TGw">
+ <rect key="frame" x="0.0" y="1" width="322" height="22"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+ <animations/>
+ <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="JWk-Ab-hdY">
+ <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ <menu key="menu" id="0GR-vf-sZq">
+ <items>
+ <menuItem title="Item 1" id="yDg-DK-z5D"/>
+ <menuItem title="Item 2" id="g4b-FM-qX4"/>
+ <menuItem title="Item 3" id="fAW-To-EeP"/>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ <connections>
+ <binding destination="EZp-rc-7mo" name="selectedIndex" keyPath="objectValue.sourceTrackIdx" previousBinding="LeD-lR-MOf" id="zso-1b-OrL"/>
+ <binding destination="EZp-rc-7mo" name="content" keyPath="objectValue.sourceTracksArray" id="LeD-lR-MOf"/>
+ </connections>
+ </popUpButton>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="forced" width="76" minWidth="10" maxWidth="3.4028229999999999e+38" id="klV-Gy-igk">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Forced Only">
@@ -60,6 +159,28 @@
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
+ <prototypeCellViews>
+ <tableCellView id="zKg-by-KFV">
+ <rect key="frame" x="326" y="1" width="76" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button id="HC5-ql-Vcr">
+ <rect key="frame" x="26" y="3" width="23" height="20"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="SPR-9q-XBK">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <binding destination="zKg-by-KFV" name="value" keyPath="objectValue.forcedOnly" id="iFe-SC-lu3"/>
+ <binding destination="zKg-by-KFV" name="enabled" keyPath="objectValue.isEnabled" id="wpB-JZ-Upd"/>
+ </connections>
+ </button>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="burned" width="64" minWidth="10" maxWidth="3.4028229999999999e+38" id="fIe-Fg-ufj">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Burned In">
@@ -71,6 +192,32 @@
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
+ <prototypeCellViews>
+ <tableCellView id="gxH-yz-YRK">
+ <rect key="frame" x="405" y="1" width="64" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button id="zpm-9Z-Hsq">
+ <rect key="frame" x="20" y="3" width="23" height="20"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="s2E-1o-mGs">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <binding destination="gxH-yz-YRK" name="value" keyPath="objectValue.burnedIn" id="reR-Z6-yBf">
+ <dictionary key="options">
+ <bool key="NSValidatesImmediately" value="YES"/>
+ </dictionary>
+ </binding>
+ <binding destination="gxH-yz-YRK" name="enabled" keyPath="objectValue.canPassthru" id="xVE-oI-pP8"/>
+ </connections>
+ </button>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="default" width="51" minWidth="10" maxWidth="3.4028229999999999e+38" id="fvq-pE-sOC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Default">
@@ -82,6 +229,28 @@
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
+ <prototypeCellViews>
+ <tableCellView id="mQ6-s6-Zm2">
+ <rect key="frame" x="472" y="1" width="51" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <button id="mdO-Qu-3Pb">
+ <rect key="frame" x="14" y="4" width="22" height="18"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <animations/>
+ <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="kwl-qH-Dgo">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <binding destination="mQ6-s6-Zm2" name="enabled" keyPath="objectValue.canPassthru" id="VIq-6A-MlG"/>
+ <binding destination="mQ6-s6-Zm2" name="value" keyPath="objectValue.def" id="6sg-s1-xTn"/>
+ </connections>
+ </button>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="srt_lang" width="173" minWidth="10" maxWidth="3.4028229999999999e+38" id="9ka-9O-WDj">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Language">
@@ -99,6 +268,40 @@
</menu>
</popUpButtonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <prototypeCellViews>
+ <tableCellView id="4bh-my-GeM">
+ <rect key="frame" x="526" y="1" width="173" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <popUpButton verticalHuggingPriority="750" id="Inz-O5-B8g">
+ <rect key="frame" x="0.0" y="0.0" width="173" height="22"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+ <animations/>
+ <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="zIn-sc-xak">
+ <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ <menu key="menu" id="YpS-Pf-abd">
+ <items>
+ <menuItem title="Item 1" id="Rj8-KI-k4L"/>
+ <menuItem title="Item 2" id="HQq-Mp-4sI"/>
+ <menuItem title="Item 3" id="yEC-jb-q8b"/>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ <connections>
+ <binding destination="4bh-my-GeM" name="enabled" keyPath="objectValue.isSrt" id="p69-lt-dgE"/>
+ <binding destination="4bh-my-GeM" name="selectedValue" keyPath="objectValue.isoLanguage" previousBinding="OIo-fF-3vU" id="Ubv-kn-nf5">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">HBIsoLanguageTrasformer</string>
+ </dictionary>
+ </binding>
+ <binding destination="4bh-my-GeM" name="content" keyPath="objectValue.languages" id="OIo-fF-3vU"/>
+ </connections>
+ </popUpButton>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="srt_charcode" width="113" minWidth="10" maxWidth="3.4028229999999999e+38" id="1Qg-We-ltR">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Char Code">
@@ -106,16 +309,42 @@
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
- <popUpButtonCell key="dataCell" type="bevel" title="Pop Up" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" continuous="YES" state="on" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="xt1-a1-JhF" id="AdJ-Mv-JI2">
+ <popUpButtonCell key="dataCell" type="bevel" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" continuous="YES" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" id="AdJ-Mv-JI2">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystem"/>
- <menu key="menu" title="OtherViews" id="ABd-Ec-K2L">
- <items>
- <menuItem title="Pop Up" state="on" id="xt1-a1-JhF"/>
- </items>
- </menu>
+ <menu key="menu" title="OtherViews" id="ABd-Ec-K2L"/>
</popUpButtonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <prototypeCellViews>
+ <tableCellView id="ZBK-cc-m0z">
+ <rect key="frame" x="702" y="1" width="113" height="25"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <popUpButton verticalHuggingPriority="750" id="QV0-kE-4yR">
+ <rect key="frame" x="0.0" y="0.0" width="113" height="22"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+ <animations/>
+ <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="jFr-2c-3Vv">
+ <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ <menu key="menu" id="u4x-JX-Hma">
+ <items>
+ <menuItem title="Item 1" id="dap-ee-kor"/>
+ <menuItem title="Item 2" id="gHY-6m-ASb"/>
+ <menuItem title="Item 3" id="ThR-Cn-Vfr"/>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ <connections>
+ <binding destination="ZBK-cc-m0z" name="selectedValue" keyPath="objectValue.charCode" previousBinding="NGt-sB-Sg2" id="eBc-20-eSF"/>
+ <binding destination="ZBK-cc-m0z" name="content" keyPath="objectValue.encodings" id="NGt-sB-Sg2"/>
+ <binding destination="ZBK-cc-m0z" name="enabled" keyPath="objectValue.isSrt" id="ZIt-gt-iqy"/>
+ </connections>
+ </popUpButton>
+ </subviews>
+ <animations/>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
<tableColumn identifier="srt_offset" width="64" minWidth="10" maxWidth="3.4028229999999999e+38" id="Fgh-pZ-6uu">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Offset">
@@ -129,16 +358,46 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ <prototypeCellViews>
+ <tableCellView id="9tV-iL-oaF">
+ <rect key="frame" x="818" y="1" width="64" height="17"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="aJi-zQ-0cg">
+ <rect key="frame" x="0.0" y="-3" width="64" height="14"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+ <animations/>
+ <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" alignment="center" title="0" id="hhH-c3-gD0">
+ <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="Jui-oB-oVO"/>
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="9tV-iL-oaF" name="enabled" keyPath="objectValue.isSrt" id="noe-Kh-7pa"/>
+ <binding destination="9tV-iL-oaF" name="value" keyPath="objectValue.offset" id="NlM-1C-Udf">
+ <dictionary key="options">
+ <integer key="NSNullPlaceholder" value="0"/>
+ </dictionary>
+ </binding>
+ </connections>
+ </textField>
+ </subviews>
+ <animations/>
+ <connections>
+ <outlet property="textField" destination="aJi-zQ-0cg" id="WSP-oS-jCs"/>
+ </connections>
+ </tableCellView>
+ </prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
+ <binding destination="ssY-jQ-Nev" name="content" keyPath="arrangedObjects" id="Oj3-29-oCf"/>
<binding destination="-2" name="enabled" keyPath="self.subtitles" id="pmZ-qP-DnG">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNotNil</string>
</dictionary>
</binding>
- <outlet property="dataSource" destination="-2" id="DH5-v0-2ba"/>
- <outlet property="delegate" destination="-2" id="Dfm-tw-89m"/>
<outlet property="menu" destination="KgC-dn-Hq2" id="Iff-7t-kdg"/>
</connections>
</tableView>
@@ -147,12 +406,12 @@
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<animations/>
- <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="toe-s0-pVk">
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="toe-s0-pVk">
<rect key="frame" x="-100" y="-100" width="685" height="15"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</scroller>
- <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="GfM-TU-Tmr">
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="GfM-TU-Tmr">
<autoresizingMask key="autoresizingMask"/>
<animations/>
</scroller>
@@ -162,81 +421,15 @@
<animations/>
</tableHeaderView>
</scrollView>
- <popUpButton verticalHuggingPriority="750" id="2Tb-KC-Ugi">
- <rect key="frame" x="17" y="291" width="88" height="22"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
- <animations/>
- <popUpButtonCell key="cell" type="push" title="Track" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="8ZD-D6-TLA">
- <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
- <font key="font" metaFont="smallSystem"/>
- <menu key="menu" title="OtherViews" id="Fr7-eG-NuL">
- <items>
- <menuItem title="Track" state="on" hidden="YES" id="TJO-RZ-jgb"/>
- <menuItem title="Add All" id="4PX-In-DpF">
- <connections>
- <action selector="addAll:" target="-2" id="tuS-uF-dje"/>
- </connections>
- </menuItem>
- <menuItem title="Add External SRT…" toolTip="Add new SRT subtitle to the list." id="HW0-PS-t0U">
- <connections>
- <action selector="browseImportSrtFile:" target="-2" id="dog-BP-my4"/>
- </connections>
- </menuItem>
- <menuItem isSeparatorItem="YES" id="2hO-bG-5qB"/>
- <menuItem title="Remove All" id="mVi-zH-KUq">
- <connections>
- <action selector="removeAll:" target="-2" id="QsA-lb-0rD"/>
- </connections>
- </menuItem>
- </items>
- </menu>
- </popUpButtonCell>
- <connections>
- <binding destination="-2" name="enabled" keyPath="self.subtitles" id="khN-Sn-dnI">
- <dictionary key="options">
- <string key="NSValueTransformerName">NSIsNotNil</string>
- </dictionary>
- </binding>
- </connections>
- </popUpButton>
- <button verticalHuggingPriority="750" id="QsM-28-Pya">
- <rect key="frame" x="110" y="288" width="140" height="28"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
- <animations/>
- <buttonCell key="cell" type="push" title="Configure Defaults…" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oxg-bs-1si">
- <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
- <font key="font" metaFont="smallSystem"/>
- </buttonCell>
- <connections>
- <action selector="showSettingsSheet:" target="-2" id="OAA-S8-tfS"/>
- <binding destination="-2" name="enabled" keyPath="self.subtitles" id="dpe-kM-iMF">
- <dictionary key="options">
- <string key="NSValueTransformerName">NSIsNotNil</string>
- </dictionary>
- </binding>
- </connections>
- </button>
- <button verticalHuggingPriority="750" id="Vxx-gk-9kY">
- <rect key="frame" x="248" y="288" width="116" height="28"/>
- <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
- <animations/>
- <buttonCell key="cell" type="push" title="Reload Defaults" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jG8-uo-1tv">
- <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
- <font key="font" metaFont="smallSystem"/>
- </buttonCell>
- <connections>
- <action selector="addTracksFromDefaults:" target="-2" id="GOz-FT-Atg"/>
- <binding destination="-2" name="enabled" keyPath="self.subtitles" id="CSa-3s-GD6">
- <dictionary key="options">
- <string key="NSValueTransformerName">NSIsNotNil</string>
- </dictionary>
- </binding>
- </connections>
- </button>
</subviews>
<animations/>
- <point key="canvasLocation" x="373" y="444"/>
+ <point key="canvasLocation" x="532" y="403"/>
</customView>
+ <arrayController objectClassName="HBSubtitlesTrack" id="ssY-jQ-Nev">
+ <connections>
+ <binding destination="-2" name="contentArray" keyPath="self.subtitles.tracks" id="mlB-F3-p89"/>
+ </connections>
+ </arrayController>
<menu id="KgC-dn-Hq2">
<items>
<menuItem title="Add All" id="S2I-Jd-Lyg">
diff --git a/macosx/HBJob+HBJobConversion.m b/macosx/HBJob+HBJobConversion.m
index b194e86d8..4ed4e4390 100644
--- a/macosx/HBJob+HBJobConversion.m
+++ b/macosx/HBJob+HBJobConversion.m
@@ -9,6 +9,8 @@
#import "HBAudioDefaults.h"
#import "HBAudioTrack.h"
+#import "HBSubtitlesTrack.h"
+
#import "HBChapter.h"
#import "HBTitlePrivate.h"
@@ -220,93 +222,84 @@
// Map the settings in the dictionaries for the SubtitleList array to match title->list_subtitle
BOOL one_burned = NO;
- for (NSDictionary *subtitleDict in self.subtitles.tracks)
+ for (HBSubtitlesTrack *subTrack in self.subtitles.tracks)
{
- int subtitle = [subtitleDict[keySubTrackIndex] intValue];
- BOOL force = [subtitleDict[keySubTrackForced] boolValue];
- BOOL burned = [subtitleDict[keySubTrackBurned] boolValue];
- BOOL def = [subtitleDict[keySubTrackDefault] boolValue];
-
- // Skip the "None" track.
- if (subtitle == -2)
- {
- continue;
- }
-
- // we need to check for the "Foreign Audio Search" which would be keySubTrackIndex of -1
- if (subtitle == -1)
+ if (subTrack.isEnabled)
{
- job->indepth_scan = 1;
+ // Shift the source index by 2 to componsate
+ // for the none and foreign audio search tracks.
+ int sourceIdx = ((int)subTrack.sourceTrackIdx) - 2;
- if (burned != 1)
+ // we need to check for the "Foreign Audio Search" which would be have an index of -1
+ if (sourceIdx == -1)
{
- job->select_subtitle_config.dest = PASSTHRUSUB;
- }
- else
- {
- job->select_subtitle_config.dest = RENDERSUB;
- }
+ job->indepth_scan = 1;
- job->select_subtitle_config.force = force;
- job->select_subtitle_config.default_track = def;
- }
- else
- {
- // if we are getting the subtitles from an external srt file
- if ([subtitleDict[keySubTrackType] intValue] == SRTSUB)
- {
- hb_subtitle_config_t sub_config;
-
- sub_config.offset = [subtitleDict[keySubTrackSrtOffset] intValue];
-
- // we need to strncpy file name and codeset
- strncpy(sub_config.src_filename, [subtitleDict[keySubTrackSrtFilePath] UTF8String], 255);
- sub_config.src_filename[255] = 0;
- strncpy(sub_config.src_codeset, [subtitleDict[keySubTrackSrtCharCode] UTF8String], 39);
- sub_config.src_codeset[39] = 0;
-
- if (!burned && hb_subtitle_can_pass(SRTSUB, job->mux))
+ if (subTrack.burnedIn)
{
- sub_config.dest = PASSTHRUSUB;
+ job->select_subtitle_config.dest = RENDERSUB;
}
- else if (hb_subtitle_can_burn(SRTSUB))
+ else
{
- // Only allow one subtitle to be burned into the video
- if (one_burned)
- continue;
- one_burned = YES;
- sub_config.dest = RENDERSUB;
+ job->select_subtitle_config.dest = PASSTHRUSUB;
}
- sub_config.force = 0;
- sub_config.default_track = def;
- hb_srt_add( job, &sub_config, [subtitleDict[keySubTrackLanguageIsoCode] UTF8String]);
- continue;
+ job->select_subtitle_config.force = subTrack.forcedOnly;
+ job->select_subtitle_config.default_track = subTrack.def;
}
-
- // We are setting a source subtitle so access the source subtitle info
- hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item(title->list_subtitle, subtitle);
-
- if (subt != NULL)
+ else
{
- hb_subtitle_config_t sub_config = subt->config;
-
- if (!burned && hb_subtitle_can_pass(subt->source, job->mux))
+ // if we are getting the subtitles from an external srt file
+ if (subTrack.type == SRTSUB)
{
- sub_config.dest = PASSTHRUSUB;
+ hb_subtitle_config_t sub_config;
+
+ sub_config.offset = subTrack.offset;
+
+ // we need to strncpy file name and codeset
+ strncpy(sub_config.src_filename, subTrack.fileURL.path.fileSystemRepresentation, 255);
+ sub_config.src_filename[255] = 0;
+ strncpy(sub_config.src_codeset, subTrack.charCode.UTF8String, 39);
+ sub_config.src_codeset[39] = 0;
+
+ if (!subTrack.burnedIn && hb_subtitle_can_pass(SRTSUB, job->mux))
+ {
+ sub_config.dest = PASSTHRUSUB;
+ }
+ else if (hb_subtitle_can_burn(SRTSUB))
+ {
+ one_burned = YES;
+ sub_config.dest = RENDERSUB;
+ }
+
+ sub_config.force = 0;
+ sub_config.default_track = subTrack.def;
+ hb_srt_add( job, &sub_config, subTrack.isoLanguage.UTF8String);
}
- else if (hb_subtitle_can_burn(subt->source))
+ else
{
- // Only allow one subtitle to be burned into the video
- if (one_burned)
- continue;
- one_burned = YES;
- sub_config.dest = RENDERSUB;
+ // We are setting a source subtitle so access the source subtitle info
+ hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item(title->list_subtitle, sourceIdx);
+
+ if (subt != NULL)
+ {
+ hb_subtitle_config_t sub_config = subt->config;
+
+ if (!subTrack.burnedIn && hb_subtitle_can_pass(subt->source, job->mux))
+ {
+ sub_config.dest = PASSTHRUSUB;
+ }
+ else if (hb_subtitle_can_burn(subt->source))
+ {
+ one_burned = YES;
+ sub_config.dest = RENDERSUB;
+ }
+
+ sub_config.force = subTrack.forcedOnly;
+ sub_config.default_track = subTrack.def;
+ hb_subtitle_add(job, &sub_config, sourceIdx);
+ }
}
-
- sub_config.force = force;
- sub_config.default_track = def;
- hb_subtitle_add(job, &sub_config, subtitle);
}
}
}
diff --git a/macosx/HBJob+UIAdditions.m b/macosx/HBJob+UIAdditions.m
index c7a7fec1f..0515ec99b 100644
--- a/macosx/HBJob+UIAdditions.m
+++ b/macosx/HBJob+UIAdditions.m
@@ -9,9 +9,12 @@
#import "HBAttributedStringAdditions.h"
#import "HBTitle.h"
#import "HBJob.h"
+
#import "HBAudioTrack.h"
#import "HBAudioDefaults.h"
+#import "HBSubtitlesTrack.h"
+
#import "HBPicture+UIAdditions.h"
#import "HBFilters+UIAdditions.h"
@@ -131,7 +134,7 @@ static NSDictionary *shortHeightAttr;
}
NSString *passesString = @"";
// check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan
- if (self.subtitles.tracks.count && [self.subtitles.tracks[0][@"keySubTrackIndex"] intValue] == -1)
+ if (self.subtitles.tracks.firstObject.sourceTrackIdx == 1)
{
passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "];
}
@@ -447,7 +450,7 @@ static NSDictionary *shortHeightAttr;
// Ninth Line Subtitle Details
int i = 0;
- for (NSDictionary *track in self.subtitles.tracks)
+ for (HBSubtitlesTrack *track in self.subtitles.tracks)
{
// Ignore the none track.
if (i == self.subtitles.tracks.count - 1)
@@ -457,16 +460,16 @@ static NSDictionary *shortHeightAttr;
/* remember that index 0 of Subtitles can contain "Foreign Audio Search*/
[finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr];
- [finalString appendString: track[@"keySubTrackName"] withAttributes:detailAttr];
- if ([track[@"keySubTrackForced"] intValue] == 1)
+ [finalString appendString: self.subtitles.sourceTracks[track.sourceTrackIdx][@"keySubTrackName"] withAttributes:detailAttr];
+ if (track.forcedOnly)
{
[finalString appendString: @" - Forced Only" withAttributes:detailAttr];
}
- if ([track[@"keySubTrackBurned"] intValue] == 1)
+ if (track.burnedIn)
{
[finalString appendString: @" - Burned In" withAttributes:detailAttr];
}
- if ([track[@"keySubTrackDefault"] intValue] == 1)
+ if (track.def)
{
[finalString appendString: @" - Default" withAttributes:detailAttr];
}
diff --git a/macosx/HBJob.m b/macosx/HBJob.m
index f03520bac..8c7b2dc63 100644
--- a/macosx/HBJob.m
+++ b/macosx/HBJob.m
@@ -95,7 +95,7 @@ NSString *HBChaptersChangedNotification = @"HBChaptersChangedNotification";
- (void)setUndo:(NSUndoManager *)undo
{
_undo = undo;
- [@[self.video, self.range, self.filters, self.picture, /*self.audio, self.subtitles*/] makeObjectsPerformSelector:@selector(setUndo:)
+ [@[self.video, self.range, self.filters, self.picture, /*self.audio,*/ self.subtitles] makeObjectsPerformSelector:@selector(setUndo:)
withObject:_undo];
[self.chapterTitles makeObjectsPerformSelector:@selector(setUndo:) withObject:_undo];
}
@@ -128,7 +128,7 @@ NSString *HBChaptersChangedNotification = @"HBChaptersChangedNotification";
_container = container;
[self.audio containerChanged:container];
- [self.subtitles containerChanged:container];
+ [self.subtitles setContainer:container];
[self.video containerChanged];
// post a notification for any interested observers to indicate that our video container has changed
diff --git a/macosx/HBSubtitles.h b/macosx/HBSubtitles.h
index 29487dd6c..0a2c56594 100644
--- a/macosx/HBSubtitles.h
+++ b/macosx/HBSubtitles.h
@@ -9,24 +9,8 @@
NS_ASSUME_NONNULL_BEGIN
-extern NSString *keySubTrackSelectionIndex;
-extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
-extern NSString *keySubTrackLanguageIsoCode;
-extern NSString *keySubTrackType;
-
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-extern NSString *keySubTrackSrtCharCodeIndex;
-extern NSString *keySubTrackLanguageIndex;
-
@class HBTitle;
+@class HBSubtitlesTrack;
@class HBSubtitlesDefaults;
@interface HBSubtitles : NSObject <NSSecureCoding, NSCopying, HBPresetCoding>
@@ -37,35 +21,26 @@ extern NSString *keySubTrackLanguageIndex;
- (void)removeAll;
- (void)reloadDefaults;
-- (void)validatePassthru;
-- (NSMutableDictionary *)createSubtitleTrack;
-- (NSMutableDictionary *)trackFromSourceTrackIndex:(NSInteger)index;
-
-@property (nonatomic, readonly) NSMutableArray *masterTrackArray; // the master list of audio tracks from the title
-@property (nonatomic, readonly) NSMutableArray *tracks;
+- (void)addSrtTrackFromURL:(NSURL *)srtURL;
-@property (nonatomic, readwrite, strong) NSString *foreignAudioSearchTrackName;
-@property (nonatomic, readonly) NSArray *charCodeArray;
-
-@property (nonatomic, readonly) NSArray *languagesArray;
-@property (nonatomic, readonly) NSInteger languagesArrayDefIndex;
+@property (nonatomic, readonly) NSMutableArray<NSDictionary *> *sourceTracks;
+@property (nonatomic, readonly) NSMutableArray<HBSubtitlesTrack *> *tracks;
@property (nonatomic, readwrite, strong) HBSubtitlesDefaults *defaults;
/**
* For internal use
*/
-
-- (void)containerChanged:(int)container;
-@property (nonatomic, readwrite) int container; // initially is the default HB_MUX_MP4
+@property (nonatomic, readwrite) int container;
+@property (nonatomic, readwrite, weak, nullable) NSUndoManager *undo;
@end
@interface HBSubtitles (KVC)
@property (nonatomic, readonly) NSUInteger countOfTracks;
-- (id)objectInTracksAtIndex:(NSUInteger)index;
-- (void)insertObject:(id)audioObject inTracksAtIndex:(NSUInteger)index;
+- (HBSubtitlesTrack *)objectInTracksAtIndex:(NSUInteger)index;
+- (void)insertObject:(HBSubtitlesTrack *)audioObject inTracksAtIndex:(NSUInteger)index;
- (void)removeObjectFromTracksAtIndex:(NSUInteger)index;
@end
diff --git a/macosx/HBSubtitles.m b/macosx/HBSubtitles.m
index 85b69ac10..8368c2c74 100644
--- a/macosx/HBSubtitles.m
+++ b/macosx/HBSubtitles.m
@@ -1,38 +1,34 @@
-//
-// HBSubtitles.m
-// HandBrake
-//
-// Created by Damiano Galassi on 12/01/15.
-//
-//
+/* HBSubtitles.m $
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
#import "HBSubtitles.h"
#import "HBSubtitlesDefaults.h"
+#import "HBSubtitlesTrack.h"
+
#import "HBTitle.h"
#import "HBCodingUtilities.h"
-#include "lang.h"
#include "common.h"
-NSString *keySubTrackSelectionIndex = @"keySubTrackSelectionIndex";
-NSString *keySubTrackName = @"keySubTrackName";
-NSString *keySubTrackIndex = @"keySubTrackIndex";
-NSString *keySubTrackLanguage = @"keySubTrackLanguage";
-NSString *keySubTrackLanguageIsoCode = @"keySubTrackLanguageIsoCode";
-NSString *keySubTrackType = @"keySubTrackType";
+extern NSString *keySubTrackName;
+extern NSString *keySubTrackLanguageIsoCode;
+extern NSString *keySubTrackType;
+
+extern NSString *keySubTrackSrtFileURL;
-NSString *keySubTrackForced = @"keySubTrackForced";
-NSString *keySubTrackBurned = @"keySubTrackBurned";
-NSString *keySubTrackDefault = @"keySubTrackDefault";
+#define NONE_TRACK_INDEX 0
+#define FOREIGN_TRACK_INDEX 1
-NSString *keySubTrackSrtOffset = @"keySubTrackSrtOffset";
-NSString *keySubTrackSrtFilePath = @"keySubTrackSrtFilePath";
-NSString *keySubTrackSrtCharCode = @"keySubTrackSrtCharCode";
-NSString *keySubTrackSrtCharCodeIndex = @"keySubTrackSrtCharCodeIndex";
-NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
+@interface HBSubtitles () <HBTrackDataSource, HBTrackDelegate>
-#define CHAR_CODE_DEFAULT_INDEX 11
+/// Used to aovid circular dependecy validation.
+@property (nonatomic, readwrite) BOOL validating;
+
+@end
@implementation HBSubtitles
@@ -43,72 +39,167 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
{
_container = HB_MUX_MP4;
+ _sourceTracks = [title.subtitlesTracks mutableCopy];
_tracks = [[NSMutableArray alloc] init];
_defaults = [[HBSubtitlesDefaults alloc] init];
- _masterTrackArray = [title.subtitlesTracks mutableCopy];
-
- NSMutableArray *forcedSourceNamesArray = [NSMutableArray array];
- for (NSDictionary *dict in _masterTrackArray)
+ NSMutableSet<NSString *> *forcedSourceNamesArray = [NSMutableSet set];
+ int foreignAudioType = VOBSUB;
+ for (NSDictionary *dict in _sourceTracks)
{
enum subsource source = [dict[keySubTrackType] intValue];
- NSString *subSourceName = @(hb_subsource_name(source));
+ NSString *name = @(hb_subsource_name(source));
// if the subtitle track can be forced, add its source name to the array
- if (hb_subtitle_can_force(source) && [forcedSourceNamesArray containsObject:subSourceName] == NO)
+ if (hb_subtitle_can_force(source) && name.length)
{
- [forcedSourceNamesArray addObject:subSourceName];
+ [forcedSourceNamesArray addObject:name];
}
}
// now set the name of the Foreign Audio Search track
+ NSMutableString *foreignAudioSearchTrackName = [@"Foreign Audio Search (Bitmap)" mutableCopy];
if (forcedSourceNamesArray.count)
{
- [forcedSourceNamesArray sortUsingComparator:^(id obj1, id obj2)
- {
- return [((NSString *)obj1) compare:((NSString *)obj2)];
- }];
+ [foreignAudioSearchTrackName appendFormat:@" ("];
+ for (NSString *name in forcedSourceNamesArray)
+ {
+ [foreignAudioSearchTrackName appendFormat:@"%@, ", name];
+ }
+ [foreignAudioSearchTrackName deleteCharactersInRange:NSMakeRange(foreignAudioSearchTrackName.length - 2, 2)];
+ [foreignAudioSearchTrackName appendFormat:@")"];
+ }
- NSString *tempList = @"";
- for (NSString *tempString in forcedSourceNamesArray)
+ // Add the none and foreign track to the source array
+ NSDictionary *none = @{ keySubTrackName: NSLocalizedString(@"None", nil)};
+ [_sourceTracks insertObject:none atIndex:0];
+
+ NSDictionary *foreign = @{ keySubTrackName: foreignAudioSearchTrackName,
+ keySubTrackType: @(foreignAudioType) };
+ [_sourceTracks insertObject:foreign atIndex:1];
+
+ }
+ return self;
+}
+
+#pragma mark - Data Source
+
+- (NSDictionary<NSString *, id> *)sourceTrackAtIndex:(NSUInteger)idx;
+{
+ return self.sourceTracks[idx];
+}
+
+- (NSArray<NSString *> *)sourceTracksArray
+{
+ NSMutableArray *sourceNames = [NSMutableArray array];
+
+ for (NSDictionary *track in self.sourceTracks)
+ {
+ [sourceNames addObject:track[keySubTrackName]];
+ }
+
+ return sourceNames;
+}
+
+#pragma mark - Delegate
+
+- (void)track:(HBSubtitlesTrack *)track didChangeSourceFrom:(NSUInteger)oldSourceIdx;
+{
+ // If the source was changed to None, remove the track
+ if (track.sourceTrackIdx == NONE_TRACK_INDEX)
+ {
+ NSUInteger idx = [self.tracks indexOfObject:track];
+ [self removeObjectFromTracksAtIndex:idx];
+ }
+ // If the source was changed to Foreign Audio Track,
+ // insert it at top if it wasn't already there
+ else if (track.sourceTrackIdx == FOREIGN_TRACK_INDEX)
+ {
+ NSUInteger idx = [self.tracks indexOfObject:track];
+ if (idx != 0)
+ {
+ [self removeObjectFromTracksAtIndex:idx];
+ if (self.tracks[0].sourceTrackIdx != FOREIGN_TRACK_INDEX)
{
- if (tempList.length)
- {
- tempList = [tempList stringByAppendingString:@", "];
- }
- tempList = [tempList stringByAppendingString:tempString];
+ [self insertObject:track inTracksAtIndex:0];
}
- self.foreignAudioSearchTrackName = [NSString stringWithFormat:@"Foreign Audio Search (Bitmap) (%@)", tempList];
}
- else
+ [self addNoneTrack];
+ }
+ // Else add a new None track
+ else if (oldSourceIdx == NONE_TRACK_INDEX)
+ {
+ [self addNoneTrack];
+ }
+ [self validatePassthru];
+}
+
+- (BOOL)canSetBurnedInOption:(HBSubtitlesTrack *)track
+{
+ BOOL result = YES;
+ for (HBSubtitlesTrack *subTrack in self.tracks)
+ {
+ if (subTrack != track && subTrack.isEnabled
+ && subTrack.sourceTrackIdx > FOREIGN_TRACK_INDEX && !subTrack.canPassthru)
{
- self.foreignAudioSearchTrackName = @"Foreign Audio Search (Bitmap)";
+ result = NO;
}
}
- return self;
+ return result;
}
-- (void)addAllTracks
+- (void)didSetBurnedInOption:(HBSubtitlesTrack *)track
{
- [self.tracks removeAllObjects];
+ if (self.validating == NO && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+ {
+ self.validating = YES;
+ NSUInteger idx = [self.tracks indexOfObject:track];
+ [self validateBurned:idx];
+ self.validating = NO;
+ }
+}
- // Add the foreign audio search pass
- [self addTrack:[self trackFromSourceTrackIndex:-1]];
+- (void)didSetDefaultOption:(HBSubtitlesTrack *)track
+{
+ if (self.validating == NO && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+ {
+ self.validating = YES;
+ NSUInteger idx = [self.tracks indexOfObject:track];
+ [self validateDefault:idx];
+ self.validating = NO;
+ }
+}
- // Add the remainings tracks
- for (NSDictionary *track in self.masterTrackArray)
+- (void)addNoneTrack
+{
+ HBSubtitlesTrack *track = [self trackFromSourceTrackIndex:NONE_TRACK_INDEX];
+ [self addTrack:track];
+}
+
+#pragma mark - Public methods
+
+- (void)addAllTracks
+{
+ while (self.countOfTracks)
{
- NSInteger sourceIndex = [track[keySubTrackIndex] integerValue];
- [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+ [self removeObjectFromTracksAtIndex:0];
}
- [self.tracks addObject:[self createSubtitleTrack]];
+ // Add the remainings tracks
+ for (NSUInteger idx = 1; idx < self.sourceTracksArray.count; idx++) {
+ [self addTrack:[self trackFromSourceTrackIndex:idx]];
+ }
+
+ [self addNoneTrack];
[self validatePassthru];
}
- (void)removeAll
{
- [self.tracks removeAllObjects];
- [self.tracks addObject:[self createSubtitleTrack]];
+ while (self.countOfTracks)
+ {
+ [self removeObjectFromTracksAtIndex:0];
+ }
+ [self addNoneTrack];
}
- (void)reloadDefaults
@@ -116,116 +207,63 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
[self addTracksFromDefaults];
}
-// This gets called whenever the video container changes.
-- (void)containerChanged:(int)container
+- (void)addSrtTrackFromURL:(NSURL *)srtURL
{
- self.container = container;
+ // Create a new entry for the subtitle source array so it shows up in our subtitle source list
+ [self.sourceTracks addObject:@{keySubTrackName: srtURL.lastPathComponent,
+ keySubTrackType: @(SRTSUB),
+ keySubTrackSrtFileURL: srtURL}];
+ HBSubtitlesTrack *track = [self trackFromSourceTrackIndex:self.sourceTracksArray.count - 1];
+ [self insertObject:track inTracksAtIndex:[self countOfTracks] - 1];
+}
- [self validatePassthru];
+- (void)setContainer:(int)container
+{
+ _container = container;
+ for (HBSubtitlesTrack *track in self.tracks)
+ {
+ track.container = container;
+ }
+ if (!(self.undo.isUndoing || self.undo.isRedoing))
+ {
+ [self validatePassthru];
+ }
+}
+
+- (void)setUndo:(NSUndoManager *)undo
+{
+ _undo = undo;
+ for (HBSubtitlesTrack *track in self.tracks)
+ {
+ track.undo = undo;
+ }
}
/**
* Convenience method to add a track to subtitlesArray.
- * It calculates the keySubTrackSelectionIndex.
*
* @param track the track to add.
*/
-- (void)addTrack:(NSMutableDictionary *)newTrack
+- (void)addTrack:(HBSubtitlesTrack *)newTrack
{
- newTrack[keySubTrackSelectionIndex] = @([newTrack[keySubTrackIndex] integerValue] + 1 + (self.tracks.count == 0));
[self insertObject:newTrack inTracksAtIndex:[self countOfTracks]];
}
/**
- * Creates a new subtitle track.
- */
-- (NSMutableDictionary *)createSubtitleTrack
-{
- NSMutableDictionary *newSubtitleTrack = [[NSMutableDictionary alloc] init];
- newSubtitleTrack[keySubTrackIndex] = @(-2);
- newSubtitleTrack[keySubTrackSelectionIndex] = @0;
- newSubtitleTrack[keySubTrackName] = @"None";
- newSubtitleTrack[keySubTrackForced] = @0;
- newSubtitleTrack[keySubTrackBurned] = @0;
- newSubtitleTrack[keySubTrackDefault] = @0;
-
- return newSubtitleTrack;
-}
-
-/**
* Creates a new track dictionary from a source track.
*
- * @param index the index of the source track in the subtitlesSourceArray,
- * -1 means a Foreign Audio Search pass.
- *
- * @return a new mutable track dictionary.
+ * @param index the index of the source track in the subtitlesSourceArray
*/
-- (NSMutableDictionary *)trackFromSourceTrackIndex:(NSInteger)index
+- (HBSubtitlesTrack *)trackFromSourceTrackIndex:(NSInteger)index
{
- NSMutableDictionary *track = [self createSubtitleTrack];
-
- if (index == -1)
- {
- /*
- * we are foreign lang search, which is inherently bitmap
- *
- * since it can be either VOBSUB or PGS and the latter can't be
- * passed through to MP4, we need to know whether there are any
- * PGS tracks in the source - otherwise we can just set the
- * source track type to VOBSUB
- */
- int subtitleTrackType = VOBSUB;
- if ([self.foreignAudioSearchTrackName rangeOfString:@(hb_subsource_name(PGSSUB))].location != NSNotFound)
- {
- subtitleTrackType = PGSSUB;
- }
- // Use -1 to indicate the foreign lang search
- track[keySubTrackIndex] = @(-1);
- track[keySubTrackName] = self.foreignAudioSearchTrackName;
- track[keySubTrackType] = @(subtitleTrackType);
- // foreign lang search is most useful when combined w/Forced Only - make it default
- track[keySubTrackForced] = @1;
- }
- else
- {
- NSDictionary *sourceTrack = self.masterTrackArray[index];
-
- track[keySubTrackIndex] = @(index);
- track[keySubTrackName] = sourceTrack[keySubTrackName];
-
- /* check to see if we are an srt, in which case set our file path and source track type kvp's*/
- if ([self.masterTrackArray[index][keySubTrackType] intValue] == SRTSUB)
- {
- track[keySubTrackType] = @(SRTSUB);
- track[keySubTrackSrtFilePath] = sourceTrack[keySubTrackSrtFilePath];
-
- track[keySubTrackLanguageIndex] = @(self.languagesArrayDefIndex);
- track[keySubTrackLanguageIsoCode] = self.languagesArray[self.languagesArrayDefIndex][1];
-
- track[keySubTrackSrtCharCodeIndex] = @(CHAR_CODE_DEFAULT_INDEX);
- track[keySubTrackSrtCharCode] = self.charCodeArray[CHAR_CODE_DEFAULT_INDEX];
- }
- else
- {
- track[keySubTrackType] = sourceTrack[keySubTrackType];
- }
- }
-
- if (!hb_subtitle_can_burn([track[keySubTrackType] intValue]))
- {
- /* the source track cannot be burned in, so uncheck the widget */
- track[keySubTrackBurned] = @0;
- }
-
- if (!hb_subtitle_can_force([track[keySubTrackType] intValue]))
- {
- /* the source track does not support forced flags, so uncheck the widget */
- track[keySubTrackForced] = @0;
- }
-
+ HBSubtitlesTrack *track = [[HBSubtitlesTrack alloc] initWithTrackIdx:index container:self.container
+ dataSource:self delegate:self];
+ track.undo = self.undo;
return track;
}
+#pragma mark - Defaults
+
/**
* Remove all the subtitles tracks and
* add new ones based on the defaults settings
@@ -236,12 +274,15 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
// so we don't add the same track twice.
NSMutableIndexSet *tracksAdded = [NSMutableIndexSet indexSet];
- [self.tracks removeAllObjects];
+ while (self.countOfTracks)
+ {
+ [self removeObjectFromTracksAtIndex:0];
+ }
// Add the foreign audio search pass
if (self.defaults.addForeignAudioSearch)
{
- [self addTrack:[self trackFromSourceTrackIndex:-1]];
+ [self addTrack:[self trackFromSourceTrackIndex:FOREIGN_TRACK_INDEX]];
}
// Add the tracks for the selected languages
@@ -249,23 +290,24 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
{
for (NSString *lang in self.defaults.trackSelectionLanguages)
{
- for (NSDictionary *track in self.masterTrackArray)
+ NSUInteger idx = 0;
+ for (NSDictionary *track in self.sourceTracks)
{
- if ([lang isEqualToString:@"und"] || [track[keySubTrackLanguageIsoCode] isEqualToString:lang])
+ if (idx > FOREIGN_TRACK_INDEX &&
+ ([lang isEqualToString:@"und"] || [track[keySubTrackLanguageIsoCode] isEqualToString:lang]))
{
- NSInteger sourceIndex = [track[keySubTrackIndex] intValue];
-
- if (![tracksAdded containsIndex:sourceIndex])
+ if (![tracksAdded containsIndex:idx])
{
- [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+ [self addTrack:[self trackFromSourceTrackIndex:idx]];
}
- [tracksAdded addIndex:sourceIndex];
+ [tracksAdded addIndex:idx];
if (self.defaults.trackSelectionBehavior == HBSubtitleTrackSelectionBehaviorFirst)
{
break;
}
}
+ idx++;
}
}
}
@@ -273,14 +315,14 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
// Add the closed captions track if there is one.
if (self.defaults.addCC)
{
- for (NSDictionary *track in self.masterTrackArray)
+ NSUInteger idx = 0;
+ for (NSDictionary *track in self.sourceTracks)
{
if ([track[keySubTrackType] intValue] == CC608SUB)
{
- NSInteger sourceIndex = [track[keySubTrackIndex] intValue];
- if (![tracksAdded containsIndex:sourceIndex])
+ if (![tracksAdded containsIndex:idx])
{
- [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+ [self addTrack:[self trackFromSourceTrackIndex:idx]];
}
if (self.defaults.trackSelectionBehavior == HBSubtitleTrackSelectionBehaviorFirst)
@@ -288,6 +330,7 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
break;
}
}
+ idx++;
}
}
@@ -296,25 +339,26 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
{
if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorFirst)
{
- if ([self.tracks.firstObject[keySubTrackIndex] integerValue] != -1)
+ if (self.tracks.firstObject.sourceTrackIdx != FOREIGN_TRACK_INDEX)
{
- self.tracks.firstObject[keySubTrackBurned] = @YES;
+ self.tracks.firstObject.burnedIn = YES;
}
else if (self.tracks.count > 1)
{
- self.tracks[1][keySubTrackBurned] = @YES;
+ self.tracks[0].burnedIn = NO;
+ self.tracks[1].burnedIn = YES;
}
}
else if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorForeignAudio)
{
- if ([self.tracks.firstObject[keySubTrackIndex] integerValue] == -1)
+ if (self.tracks.firstObject.sourceTrackIdx == FOREIGN_TRACK_INDEX)
{
- self.tracks.firstObject[keySubTrackBurned] = @YES;
+ self.tracks.firstObject.burnedIn = YES;
}
}
else if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorForeignAudioThenFirst)
{
- self.tracks.firstObject[keySubTrackBurned] = @YES;
+ self.tracks.firstObject.burnedIn = YES;
}
}
@@ -325,18 +369,18 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
BOOL bitmapSubtitlesFound = NO;
NSMutableArray *tracksToDelete = [[NSMutableArray alloc] init];
- for (NSMutableDictionary *track in self.tracks)
+ for (HBSubtitlesTrack *track in self.tracks)
{
- if ([track[keySubTrackIndex] integerValue] != -1)
+ if (track.sourceTrackIdx != 1)
{
- if ((([track[keySubTrackType] intValue] == VOBSUB && self.defaults.burnInDVDSubtitles) ||
- ([track[keySubTrackType] intValue] == PGSSUB && self.defaults.burnInBluraySubtitles)) &&
+ if (((track.type == VOBSUB && self.defaults.burnInDVDSubtitles) ||
+ (track.type == PGSSUB && self.defaults.burnInBluraySubtitles)) &&
!bitmapSubtitlesFound)
{
- track[keySubTrackBurned] = @YES;
+ track.burnedIn = YES;
bitmapSubtitlesFound = YES;
}
- else if ([track[keySubTrackType] intValue] == VOBSUB || [track[keySubTrackType] intValue] == PGSSUB)
+ else if (track.type == VOBSUB || track.type == PGSSUB)
{
[tracksToDelete addObject:track];
}
@@ -346,10 +390,12 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
}
// Add an empty track
- [self insertObject:[self createSubtitleTrack] inTracksAtIndex:[self countOfTracks]];
+ [self addNoneTrack];
[self validatePassthru];
}
+#pragma mark - Validation
+
/**
* Checks whether any subtitles in the list cannot be passed through.
* Set the first of any such subtitles to burned-in, remove the others.
@@ -357,103 +403,70 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
- (void)validatePassthru
{
BOOL convertToBurnInUsed = NO;
- NSMutableArray *tracksToDelete = [[NSMutableArray alloc] init];
+ NSMutableIndexSet *tracksToDelete = [[NSMutableIndexSet alloc] init];
// convert any non-None incompatible tracks to burn-in or remove them
- for (NSMutableDictionary *track in self.tracks)
+ NSUInteger idx = 0;
+ for (HBSubtitlesTrack *track in self.tracks)
{
- if (track[keySubTrackType] == nil)
- {
- continue;
- }
-
- int subtitleTrackType = [track[keySubTrackType] intValue];
- if (!hb_subtitle_can_pass(subtitleTrackType, self.container))
+ if (track.isEnabled && track.sourceTrackIdx > FOREIGN_TRACK_INDEX && !track.canPassthru)
{
if (convertToBurnInUsed == NO)
{
//we haven't set any track to burned-in yet, so we can
- track[keySubTrackBurned] = @1;
+ track.burnedIn = YES;
convertToBurnInUsed = YES; //remove any additional tracks
}
else
{
//we already have a burned-in track, we must remove others
- [tracksToDelete addObject:track];
+ [tracksToDelete addIndex:idx];
}
}
+ idx++;
}
+
//if we converted a track to burned-in, unset it for tracks that support passthru
if (convertToBurnInUsed == YES)
{
- for (NSMutableDictionary *track in self.tracks)
+ for (HBSubtitlesTrack *track in self.tracks)
{
- if (track[keySubTrackType] == nil)
+ if (track.isEnabled && track.sourceTrackIdx > FOREIGN_TRACK_INDEX && track.canPassthru)
{
- continue;
- }
-
- int subtitleTrackType = [track[keySubTrackType] intValue];
- if (hb_subtitle_can_pass(subtitleTrackType, self.container))
- {
- track[keySubTrackBurned] = @0;
+ track.burnedIn = NO;
}
}
}
- [self willChangeValueForKey:@"tracks"];
- if (tracksToDelete.count)
- {
- [self.tracks removeObjectsInArray:tracksToDelete];
- }
- [self didChangeValueForKey:@"tracks"];
-
-}
-
-#pragma mark - Languages
-
-@synthesize languagesArray = _languagesArray;
-
-- (NSArray *)languagesArray
-{
- if (!_languagesArray)
+ // Delete the tracks
+ NSUInteger currentIndex = [tracksToDelete lastIndex];
+ while (currentIndex != NSNotFound)
{
- _languagesArray = [self populateLanguageArray];
+ [self removeObjectFromTracksAtIndex:currentIndex];
+ currentIndex = [tracksToDelete indexLessThanIndex:currentIndex];
}
-
- return _languagesArray;
}
-- (NSArray *)populateLanguageArray
+- (void)validateBurned:(NSUInteger)index
{
- NSMutableArray *languages = [[NSMutableArray alloc] init];
-
- for (const iso639_lang_t * lang = lang_get_next(NULL); lang != NULL; lang = lang_get_next(lang))
+ [self.tracks enumerateObjectsUsingBlock:^(HBSubtitlesTrack *track, NSUInteger idx, BOOL *stop)
{
- [languages addObject:@[@(lang->eng_name),
- @(lang->iso639_2)]];
- if (!strcasecmp(lang->eng_name, "English"))
+ if (idx != index && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
{
- _languagesArrayDefIndex = [languages count] - 1;
+ track.burnedIn = NO;
}
- }
- return [languages copy];
+ }];
}
-@synthesize charCodeArray = _charCodeArray;
-
-- (NSArray *)charCodeArray
+- (void)validateDefault:(NSUInteger)index
{
- if (!_charCodeArray)
+ [self.tracks enumerateObjectsUsingBlock:^(HBSubtitlesTrack *obj, NSUInteger idx, BOOL *stop)
{
- // populate the charCodeArray.
- _charCodeArray = @[@"ANSI_X3.4-1968", @"ANSI_X3.4-1986", @"ANSI_X3.4", @"ANSI_X3.110-1983", @"ANSI_X3.110", @"ASCII",
- @"ECMA-114", @"ECMA-118", @"ECMA-128", @"ECMA-CYRILLIC", @"IEC_P27-1", @"ISO-8859-1", @"ISO-8859-2",
- @"ISO-8859-3", @"ISO-8859-4", @"ISO-8859-5", @"ISO-8859-6", @"ISO-8859-7", @"ISO-8859-8", @"ISO-8859-9",
- @"ISO-8859-9E", @"ISO-8859-10", @"ISO-8859-11", @"ISO-8859-13", @"ISO-8859-14", @"ISO-8859-15", @"ISO-8859-16",
- @"UTF-7", @"UTF-8", @"UTF-16", @"UTF-16LE", @"UTF-16BE", @"UTF-32", @"UTF-32LE", @"UTF-32BE"];
- }
- return _charCodeArray;
+ if (idx != index && obj.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+ {
+ obj.def = NO;
+ }
+ }];
}
#pragma mark - NSCopying
@@ -465,15 +478,13 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
if (copy)
{
copy->_container = _container;
-
- copy->_masterTrackArray = [_masterTrackArray mutableCopy];
- copy->_foreignAudioSearchTrackName = [_foreignAudioSearchTrackName copy];
+ copy->_sourceTracks = [_sourceTracks mutableCopy];
copy->_tracks = [[NSMutableArray alloc] init];
[_tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (idx < _tracks.count)
{
- NSMutableDictionary *trackCopy = [obj copy];
+ id trackCopy = [obj copy];
[copy->_tracks addObject:trackCopy];
}
}];
@@ -493,14 +504,11 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
- (void)encodeWithCoder:(NSCoder *)coder
{
- [coder encodeInt:1 forKey:@"HBAudioVersion"];
+ [coder encodeInt:1 forKey:@"HBSubtitlesVersion"];
encodeInt(_container);
-
- encodeObject(_masterTrackArray);
- encodeObject(_foreignAudioSearchTrackName);
+ encodeObject(_sourceTracks);
encodeObject(_tracks);
-
encodeObject(_defaults);
}
@@ -509,11 +517,15 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
self = [super init];
decodeInt(_container);
-
- decodeObject(_masterTrackArray, NSMutableArray);
- decodeObject(_foreignAudioSearchTrackName, NSString);
+ decodeObject(_sourceTracks, NSMutableArray);
decodeObject(_tracks, NSMutableArray);
+ for (HBSubtitlesTrack *track in _tracks)
+ {
+ track.dataSource = self;
+ track.delegate = self;
+ }
+
decodeObject(_defaults, HBSubtitlesDefaults);
return self;
@@ -535,23 +547,26 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
#pragma mark -
#pragma mark KVC
-- (NSUInteger) countOfTracks
+- (NSUInteger)countOfTracks
{
return self.tracks.count;
}
-- (id)objectInTracksAtIndex:(NSUInteger)index
+- (HBSubtitlesTrack *)objectInTracksAtIndex:(NSUInteger)index
{
return self.tracks[index];
}
-- (void)insertObject:(id)track inTracksAtIndex:(NSUInteger)index;
+- (void)insertObject:(HBSubtitlesTrack *)track inTracksAtIndex:(NSUInteger)index;
{
+ [[self.undo prepareWithInvocationTarget:self] removeObjectFromTracksAtIndex:index];
[self.tracks insertObject:track atIndex:index];
}
- (void)removeObjectFromTracksAtIndex:(NSUInteger)index
{
+ HBSubtitlesTrack *track = self.tracks[index];
+ [[self.undo prepareWithInvocationTarget:self] insertObject:track inTracksAtIndex:index];
[self.tracks removeObjectAtIndex:index];
}
diff --git a/macosx/HBSubtitlesController.m b/macosx/HBSubtitlesController.m
index 509ab0de5..3fb0b7f50 100644
--- a/macosx/HBSubtitlesController.m
+++ b/macosx/HBSubtitlesController.m
@@ -10,23 +10,11 @@
#import "HBSubtitles.h"
#import "HBSubtitlesDefaults.h"
-#include "hb.h"
-#include "lang.h"
-
-static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
-
-@interface HBSubtitlesController () <NSTableViewDataSource, NSTableViewDelegate>
-
-// IBOutles
-@property (unsafe_unretained) IBOutlet NSTableView *fTableView;
+@interface HBSubtitlesController ()
// Defaults
@property (nonatomic, readwrite, strong) HBSubtitlesDefaultsController *defaultsController;
-// Cached table view's cells
-@property (nonatomic, readonly) NSPopUpButtonCell *languagesCell;
-@property (nonatomic, readonly) NSPopUpButtonCell *encodingsCell;
-
@end
@implementation HBSubtitlesController
@@ -34,41 +22,12 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
- (instancetype)init
{
self = [super initWithNibName:@"Subtitles" bundle:nil];
-
- [self addObserver:self forKeyPath:@"self.subtitles.tracks" options:NSKeyValueObservingOptionInitial context:HBSubtitlesControllerContext];
-
return self;
}
-#pragma mark - KVO
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
- if (context == HBSubtitlesControllerContext)
- {
- // We use KVO to update the table manually
- // because this table isn't using bindings
- if ([keyPath isEqualToString:@"self.subtitles.tracks"])
- {
- [self.fTableView reloadData];
- }
- }
- else
- {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
-}
-
-- (void)setSubtitles:(HBSubtitles *)subtitles
-{
- _subtitles = subtitles;
-
- [self.fTableView reloadData];
-}
-
#pragma mark - Actions
-- (BOOL)validateUserInterfaceItem:(id < NSValidatedUserInterfaceItem >)anItem
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
{
return (self.subtitles != nil);
}
@@ -79,7 +38,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
- (IBAction)addAll:(id)sender
{
[self.subtitles addAllTracks];
- [self.fTableView reloadData];
}
/**
@@ -88,7 +46,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
- (IBAction)removeAll:(id)sender
{
[self.subtitles removeAll];
- [self.fTableView reloadData];
}
/**
@@ -98,15 +55,14 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
- (IBAction)addTracksFromDefaults:(id)sender
{
[self.subtitles reloadDefaults];
- [self.fTableView reloadData];
}
- (IBAction)showSettingsSheet:(id)sender
{
self.defaultsController = [[HBSubtitlesDefaultsController alloc] initWithSettings:self.subtitles.defaults];
- [NSApp beginSheet:[self.defaultsController window]
- modalForWindow:[[self view] window]
+ [NSApp beginSheet:self.defaultsController.window
+ modalForWindow:self.view.window
modalDelegate:self
didEndSelector:@selector(sheetDidEnd)
contextInfo:NULL];
@@ -117,388 +73,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
self.defaultsController = nil;
}
-#pragma mark - Subtitles tracks creation and validation
-
-/**
- * Checks whether any subtitles in the list cannot be passed through.
- * Set the first of any such subtitles to burned-in, remove the others.
- */
-- (void)validatePassthru
-{
- [self.subtitles validatePassthru];
- [self.fTableView reloadData];
-}
-
-- (void)validateBurned:(NSInteger)index
-{
- [self.subtitles.tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
- {
- if (idx != index)
- {
- obj[keySubTrackBurned] = @0;
- }
- }];
- [self validatePassthru];
-}
-
-- (void)validateDefault:(NSInteger)index
-{
- [self.subtitles.tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
- {
- if (idx != index)
- {
- obj[keySubTrackDefault] = @0;
- }
- }];
-}
-
-#pragma mark -
-#pragma mark Subtitle Table Data Source Methods
-
-- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
-{
- return self.subtitles.tracks.count;
-}
-
-- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- NSDictionary *track = self.subtitles.tracks[rowIndex];
-
- if ([[aTableColumn identifier] isEqualToString:@"track"])
- {
- NSNumber *index = track[keySubTrackSelectionIndex];
- if (index)
- return index;
- else
- return @0;
- }
- else if ([[aTableColumn identifier] isEqualToString:@"forced"])
- {
- return track[keySubTrackForced];
- }
- else if ([[aTableColumn identifier] isEqualToString:@"burned"])
- {
- return track[keySubTrackBurned];
- }
- else if ([[aTableColumn identifier] isEqualToString:@"default"])
- {
- return track[keySubTrackDefault];
- }
- /* These next three columns only apply to srt's. they are disabled for source subs */
- else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
- {
- if ([track[keySubTrackType] intValue] == SRTSUB)
- {
- return track[keySubTrackLanguageIndex];
- }
- else
- {
- return @0;
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
- {
- if ([track[keySubTrackType] intValue] == SRTSUB)
- {
- return track[keySubTrackSrtCharCodeIndex];
- }
- else
- {
- return @0;
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
- {
- if (track[keySubTrackSrtOffset])
- {
- return [track[keySubTrackSrtOffset] stringValue];
- }
- else
- {
- return @"0";
- }
- }
-
- return nil;
-}
-
-/**
- * Called whenever a widget in the table is edited or changed, we use it to record the change in the controlling array
- * including removing and adding new tracks via the "None" ("track" index of 0)
- */
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- if ([[aTableColumn identifier] isEqualToString:@"track"])
- {
- /* Set the array to track if we are vobsub (picture sub) */
- if ([anObject intValue] > 0)
- {
- NSMutableDictionary *newTrack = [self.subtitles trackFromSourceTrackIndex:[anObject integerValue] - 1 - (rowIndex == 0)];
- // Selection index calculation
- newTrack[keySubTrackSelectionIndex] = @([anObject integerValue]);
- self.subtitles.tracks[rowIndex] = newTrack;
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"forced"])
- {
- self.subtitles.tracks[rowIndex][keySubTrackForced] = @([anObject intValue]);
- }
- else if ([[aTableColumn identifier] isEqualToString:@"burned"])
- {
- self.subtitles.tracks[rowIndex][keySubTrackBurned] = @([anObject intValue]);
- if([anObject intValue] == 1)
- {
- /* Burned In and Default are mutually exclusive */
- self.subtitles.tracks[rowIndex][keySubTrackDefault] = @0;
- }
- /* now we need to make sure no other tracks are set to burned if we have set burned */
- if ([anObject intValue] == 1)
- {
- [self validateBurned:rowIndex];
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"default"])
- {
- self.subtitles.tracks[rowIndex][keySubTrackDefault] = @([anObject intValue]);
- if([anObject intValue] == 1)
- {
- /* Burned In and Default are mutually exclusive */
- self.subtitles.tracks[rowIndex][keySubTrackBurned] = @0;
- }
- /* now we need to make sure no other tracks are set to default */
- if ([anObject intValue] == 1)
- {
- [self validateDefault:rowIndex];
- }
- }
- /* These next three columns only apply to srt's. they are disabled for source subs */
- else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
- {
- self.subtitles.tracks[rowIndex][keySubTrackLanguageIndex] = @([anObject intValue]);
- self.subtitles.tracks[rowIndex][keySubTrackLanguageIsoCode] = self.subtitles.languagesArray[[anObject intValue]][1];
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
- {
- /* charCodeArray */
- self.subtitles.tracks[rowIndex][keySubTrackSrtCharCodeIndex] = @([anObject intValue]);
- self.subtitles.tracks[rowIndex][keySubTrackSrtCharCode] = self.subtitles.charCodeArray[[anObject intValue]];
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
- {
- self.subtitles.tracks[rowIndex][keySubTrackSrtOffset] = @([anObject integerValue]);
- }
-
- /* now lets do a bit of logic to add / remove tracks as necessary via the "None" track (index 0) */
- if ([[aTableColumn identifier] isEqualToString:@"track"])
- {
- /* Since currently no quicktime based playback devices support soft vobsubs in mp4, we make sure "burned in" is specified
- * by default to avoid massive confusion and anarchy. However we also want to guard against multiple burned in subtitle tracks
- * as libhb would ignore all but the first one anyway. Plus it would probably be stupid.
- */
- if ((self.subtitles.container & HB_MUX_MASK_MP4) && ([anObject intValue] != 0))
- {
- if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == VOBSUB)
- {
- /* lets see if there are currently any burned in subs specified */
- BOOL subtrackBurnedInFound = NO;
- for (id tempObject in self.subtitles.tracks)
- {
- if ([tempObject[keySubTrackBurned] intValue] == 1)
- {
- subtrackBurnedInFound = YES;
- }
- }
- /* if we have no current vobsub set to burn it in ... burn it in by default */
- if (!subtrackBurnedInFound)
- {
- self.subtitles.tracks[rowIndex][keySubTrackBurned] = @1;
- /* Burned In and Default are mutually exclusive */
- self.subtitles.tracks[rowIndex][keySubTrackDefault] = @0;
- }
- }
- }
-
- /* We use the track popup index number (presumes index 0 is "None" which is ignored and only used to remove tracks if need be)
- * to determine whether to 1 modify an existing track, 2. add a new empty "None" track or 3. remove an existing track.
- */
-
- if ([anObject intValue] != 0 && rowIndex == [self.subtitles.tracks count] - 1) // if we have a last track which != "None"
- {
- /* add a new empty None track */
- [self.subtitles.tracks addObject:[self.subtitles createSubtitleTrack]];
- }
- else if ([anObject intValue] == 0 && rowIndex != ([self.subtitles.tracks count] -1))// if this track is set to "None" and not the last track displayed
- {
- /* we know the user chose to remove this track by setting it to None, so remove it from the array */
- /* However,if this is the first track we have to reset the selected index of the next track by + 1, since it will now become
- * the first track, which has to account for the extra "Foreign Language Search" index. */
- if (rowIndex == 0 && [self.subtitles.tracks[1][keySubTrackSelectionIndex] intValue] != 0)
- {
- /* get the index of the selection in row one (which is track two) */
- int trackOneSelectedIndex = [self.subtitles.tracks[1][keySubTrackSelectionIndex] intValue];
- /* increment the index of the subtitle menu item by one, to account for Foreign Language Search which is unique to the first track */
- self.subtitles.tracks[1][keySubTrackSelectionIndex] = @(trackOneSelectedIndex + 1);
- }
- /* now that we have made the adjustment for track one (index 0) go ahead and delete the track */
- [self.subtitles.tracks removeObjectAtIndex: rowIndex];
- }
-
- // Validate the current passthru tracks.
- [self validatePassthru];
- }
-
- [aTableView reloadData];
-}
-
-#pragma mark -
-#pragma mark Subtitle Table Delegate Methods
-
-- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
-{
- if ([[tableColumn identifier] isEqualToString:@"track"])
- {
- // 'track' is a popup of all available source subtitle tracks for the given title
- NSPopUpButtonCell *cellTrackPopup = [[NSPopUpButtonCell alloc] init];
- [cellTrackPopup setControlSize:NSSmallControlSize];
- [cellTrackPopup setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
-
- // Add our initial "None" track which we use to add source tracks or remove tracks.
- // "None" is always index 0.
- [[cellTrackPopup menu] addItemWithTitle:@"None" action:NULL keyEquivalent:@""];
-
- // Foreign Audio Search (index 1 in the popup) is only available for the first track
- if (rowIndex == 0)
- {
- [[cellTrackPopup menu] addItemWithTitle:self.subtitles.foreignAudioSearchTrackName action:NULL keyEquivalent:@""];
- }
-
- for (NSDictionary *track in self.subtitles.masterTrackArray)
- {
- [[cellTrackPopup menu] addItemWithTitle:track[keySubTrackName] action:NULL keyEquivalent:@""];
- }
-
- return cellTrackPopup;
- }
- else if ([[tableColumn identifier] isEqualToString:@"srt_lang"])
- {
- return self.languagesCell;
- }
- else if ([[tableColumn identifier] isEqualToString:@"srt_charcode"])
- {
- return self.encodingsCell;
- }
-
- return nil;
-}
-
-/**
- * Enables/Disables the table view cells.
- */
-- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
- if ([[aTableColumn identifier] isEqualToString:@"track"])
- {
- return;
- }
-
- // If the Track is None, we disable the other cells as None is an empty track
- if ([self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] == 0)
- {
- [aCell setEnabled:NO];
- }
- else
- {
- // Since we have a valid track, we go ahead and enable the rest of the widgets and set them according to the controlling array */
- [aCell setEnabled:YES];
- }
-
- if ([[aTableColumn identifier] isEqualToString:@"forced"])
- {
- // Disable the "Forced Only" checkbox if a) the track is "None" or b) the subtitle track doesn't support forced flags
- if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
- !hb_subtitle_can_force([self.subtitles.tracks[rowIndex][keySubTrackType] intValue]))
- {
- [aCell setEnabled:NO];
- }
- else
- {
- [aCell setEnabled:YES];
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"burned"])
- {
- /*
- * Disable the "Burned In" checkbox if:
- * a) the track is "None" OR
- * b) the subtitle track can't be burned in OR
- * c) the subtitle track can't be passed through (e.g. PGS w/MP4)
- */
- int subtitleTrackType = [self.subtitles.tracks[rowIndex][keySubTrackType] intValue];
- if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
- !hb_subtitle_can_burn(subtitleTrackType) || !hb_subtitle_can_pass(subtitleTrackType, self.subtitles.container))
- {
- [aCell setEnabled:NO];
- }
- else
- {
- [aCell setEnabled:YES];
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"default"])
- {
- /*
- * Disable the "Default" checkbox if:
- * a) the track is "None" OR
- * b) the subtitle track can't be passed through (e.g. PGS w/MP4)
- */
- if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
- !hb_subtitle_can_pass([self.subtitles.tracks[rowIndex][keySubTrackType] intValue], self.subtitles.container))
- {
- [aCell setEnabled:NO];
- }
- else
- {
- [aCell setEnabled:YES];
- }
- }
- /* These next three columns only apply to srt's. they are disabled for source subs */
- else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
- {
- /* We have an srt file so set the track type (Source or SRT, and the srt file path ) kvp's*/
- if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
- {
- [aCell setEnabled:YES];
- }
- else
- {
- [aCell setEnabled:NO];
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
- {
- /* We have an srt file so set the track type (Source or SRT, and the srt file path ) kvp's*/
- if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
- {
- [aCell setEnabled:YES];
- }
- else
- {
- [aCell setEnabled:NO];
- }
- }
- else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
- {
- if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
- {
- [aCell setEnabled:YES];
- }
- else
- {
- [aCell setEnabled:NO];
- }
- }
-}
-
#pragma mark - Srt import
/**
@@ -509,92 +83,33 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
- (IBAction)browseImportSrtFile:(id)sender
{
NSOpenPanel *panel = [NSOpenPanel openPanel];
- [panel setAllowsMultipleSelection:NO];
- [panel setCanChooseFiles:YES];
- [panel setCanChooseDirectories:NO];
+ panel.allowsMultipleSelection = NO;
+ panel.canChooseFiles = YES;
+ panel.canChooseDirectories = NO;
NSURL *sourceDirectory;
- if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"])
- {
- sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"];
- }
- else
- {
- sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
- }
-
- /* we open up the browse srt sheet here and call for browseImportSrtFileDone after the sheet is closed */
- NSArray *fileTypes = @[@"plist", @"srt"];
- [panel setDirectoryURL:sourceDirectory];
- [panel setAllowedFileTypes:fileTypes];
- [panel beginSheetModalForWindow:[[self view] window] completionHandler:^(NSInteger result) {
- if (result == NSOKButton)
- {
- NSURL *importSrtFileURL = [panel URL];
- NSURL *importSrtDirectory = [importSrtFileURL URLByDeletingLastPathComponent];
- [[NSUserDefaults standardUserDefaults] setURL:importSrtDirectory forKey:@"LastSrtImportDirectoryURL"];
-
- /* Create a new entry for the subtitle source array so it shows up in our subtitle source list */
- NSString *displayname = [importSrtFileURL lastPathComponent];// grok an appropriate display name from the srt subtitle */
-
- /* create a dictionary of source subtitle information to store in our array */
- [self.subtitles.masterTrackArray addObject:@{keySubTrackIndex: @(self.subtitles.masterTrackArray.count),
- keySubTrackName: displayname,
- keySubTrackType: @(SRTSUB),
- keySubTrackSrtFilePath: importSrtFileURL.path}];
-
- // Now create a new srt subtitle dictionary assuming the user wants to add it to their list
- NSMutableDictionary *newSubtitleSrtTrack = [self.subtitles trackFromSourceTrackIndex:self.subtitles.masterTrackArray.count - 1];
- // Calculate the pop up selection index
- newSubtitleSrtTrack[keySubTrackSelectionIndex] = @(self.subtitles.masterTrackArray.count + (self.subtitles.tracks.count == 1));
- [self.subtitles.tracks insertObject:newSubtitleSrtTrack atIndex:self.subtitles.tracks.count - 1];
-
- [self.fTableView reloadData];
- }
- }];
-}
-
-#pragma mark - UI cells
-
-@synthesize languagesCell = _languagesCell;
-
-- (NSPopUpButtonCell *)languagesCell
-{
- if (!_languagesCell)
+ if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"])
{
- // 'srt_lang' is a popup of commonly used languages to be matched to the source srt file
- _languagesCell = [[NSPopUpButtonCell alloc] init];
- // Set the Popups properties
- [_languagesCell setControlSize:NSSmallControlSize];
- [_languagesCell setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
-
- // list our languages as per the languagesArray
- for (NSArray *lang in self.subtitles.languagesArray)
- {
- [[_languagesCell menu] addItemWithTitle:lang[0] action:NULL keyEquivalent:@""];
- }
+ sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"];
+ }
+ else
+ {
+ sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
}
- return _languagesCell;
-}
-
-@synthesize encodingsCell = _encodingsCell;
-- (NSPopUpButtonCell *)encodingsCell
-{
- if (!_encodingsCell) {
- // 'srt_charcode' is a popup of commonly used character codes to be matched to the source srt file
- _encodingsCell = [[NSPopUpButtonCell alloc] init];
- // Set the Popups properties
- [_encodingsCell setControlSize:NSSmallControlSize];
- [_encodingsCell setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
+ panel.directoryURL = sourceDirectory;
+ panel.allowedFileTypes = @[@"srt"];
- // list our character codes, as per charCodeArray
- for (NSString *charCode in self.subtitles.charCodeArray)
+ [panel beginSheetModalForWindow:self.view.window completionHandler:^(NSInteger result)
+ {
+ if (result == NSFileHandlingPanelOKButton)
{
- [[_encodingsCell menu] addItemWithTitle:charCode action: NULL keyEquivalent: @""];
+ NSURL *importSrtFileURL = panel.URL;
+ NSURL *importSrtDirectory = importSrtFileURL.URLByDeletingLastPathComponent;
+ [[NSUserDefaults standardUserDefaults] setURL:importSrtDirectory forKey:@"LastSrtImportDirectoryURL"];
+
+ [self.subtitles addSrtTrackFromURL:importSrtFileURL];
}
- }
- return _encodingsCell;
+ }];
}
-
@end
diff --git a/macosx/HBSubtitlesTrack.h b/macosx/HBSubtitlesTrack.h
new file mode 100644
index 000000000..d093a801b
--- /dev/null
+++ b/macosx/HBSubtitlesTrack.h
@@ -0,0 +1,85 @@
+/* HBSubtitlesTrack.h
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class HBSubtitlesTrack;
+
+/**
+ * HBTrackDataSource
+ */
+@protocol HBTrackDataSource <NSObject>
+- (NSDictionary<NSString *, id> *)sourceTrackAtIndex:(NSUInteger)idx;
+- (NSArray<NSString *> *)sourceTracksArray;
+@end
+
+/**
+ * HBTrackDelegate
+ */
+@protocol HBTrackDelegate <NSObject>
+- (void)track:(HBSubtitlesTrack *)track didChangeSourceFrom:(NSUInteger)oldSourceIdx;
+
+- (BOOL)canSetBurnedInOption:(HBSubtitlesTrack *)track;
+- (void)didSetBurnedInOption:(HBSubtitlesTrack *)track;
+
+- (void)didSetDefaultOption:(HBSubtitlesTrack *)track;
+@end
+
+@interface HBSubtitlesTrack : NSObject <NSSecureCoding, NSCopying>
+
+- (instancetype)initWithTrackIdx:(NSUInteger)index
+ container:(int)container
+ dataSource:(id<HBTrackDataSource>)dataSource
+ delegate:(id<HBTrackDelegate>)delegate;
+
+/// The index of the source in the data source tracks array.
+@property (nonatomic, readonly) NSUInteger sourceTrackIdx;
+/// Format.
+@property (nonatomic, readonly) int type;
+@property (nonatomic, readwrite) int container;
+
+/// Whether to use only the forced subtitles of the track.
+@property (nonatomic, readwrite) BOOL forcedOnly;
+/// Whether the track should be burned.
+@property (nonatomic, readwrite) BOOL burnedIn;
+/// Whether is the default track.
+@property (nonatomic, readwrite) BOOL def;
+
+/// The URL of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSURL *fileURL;
+/// The ISO 639/2 language code of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSString *isoLanguage;
+/// The character encoding of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSString *charCode;
+/// The offset in milliseconds of the external subtitles file.
+@property (nonatomic, readwrite) int offset;
+
+@property (nonatomic, readwrite, weak, nullable) NSUndoManager *undo;
+
+@property (nonatomic, readwrite, weak) id<HBTrackDataSource> dataSource;
+@property (nonatomic, readwrite, weak) id<HBTrackDelegate> delegate;
+
+/// A complete list of the possible languages.
+- (NSArray<NSString *> *)languages;
+/// A complete list of the possible encodings.
+- (NSArray<NSString *> *)encodings;
+
+@property (nonatomic, readonly) BOOL isSrt;
+@property (nonatomic, readonly) BOOL isEnabled;
+@property (nonatomic, readonly) BOOL canPassthru;
+
+@end
+
+/**
+ * HBIsoLanguageTrasformer is a trasformer to transform
+ * a ISO 639/2 code to a human readable language name.
+ */
+@interface HBIsoLanguageTrasformer : NSValueTransformer
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/macosx/HBSubtitlesTrack.m b/macosx/HBSubtitlesTrack.m
new file mode 100644
index 000000000..dfa69869e
--- /dev/null
+++ b/macosx/HBSubtitlesTrack.m
@@ -0,0 +1,420 @@
+/* HBSubtitlesTrack.m
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+#import "HBSubtitlesTrack.h"
+#import "HBCodingUtilities.h"
+
+#include "common.h"
+#include "lang.h"
+
+#define CHAR_CODE_DEFAULT_INDEX 11
+
+static NSArray *charEncodingArray = nil;
+static NSArray *_languagesArray = nil;
+
+NSString *keySubTrackName = @"keySubTrackName";
+NSString *keySubTrackLanguageIsoCode = @"keySubTrackLanguageIsoCode";
+NSString *keySubTrackType = @"keySubTrackType";
+NSString *keySubTrackSrtFileURL = @"keySubTrackSrtFileURL";
+
+@interface HBSubtitlesTrack ()
+@property (nonatomic, readwrite) BOOL validating;
+@end
+
+@implementation HBSubtitlesTrack
+
++ (void)initialize
+{
+ if ([HBSubtitlesTrack class] == self)
+ {
+ // populate the charCodeArray.
+ charEncodingArray = @[@"ANSI_X3.4-1968", @"ANSI_X3.4-1986", @"ANSI_X3.4", @"ANSI_X3.110-1983", @"ANSI_X3.110",
+ @"ASCII", @"ECMA-114", @"ECMA-118", @"ECMA-128", @"ECMA-CYRILLIC", @"IEC_P27-1", @"ISO-8859-1",
+ @"ISO-8859-2", @"ISO-8859-3", @"ISO-8859-4", @"ISO-8859-5", @"ISO-8859-6", @"ISO-8859-7",
+ @"ISO-8859-8", @"ISO-8859-9", @"ISO-8859-9E", @"ISO-8859-10", @"ISO-8859-11", @"ISO-8859-13",
+ @"ISO-8859-14", @"ISO-8859-15", @"ISO-8859-16", @"UTF-7", @"UTF-8", @"UTF-16", @"UTF-16LE",
+ @"UTF-16BE", @"UTF-32", @"UTF-32LE", @"UTF-32BE"];
+
+ // populate the languages array
+ NSMutableArray *languages = [[NSMutableArray alloc] init];
+ for (const iso639_lang_t *lang = lang_get_next(NULL); lang != NULL; lang = lang_get_next(lang))
+ {
+ [languages addObject:@(lang->eng_name)];
+ }
+ _languagesArray = [languages copy];
+ }
+}
+
+- (instancetype)initWithTrackIdx:(NSUInteger)index
+ container:(int)container
+ dataSource:(id<HBTrackDataSource>)dataSource
+ delegate:(id<HBTrackDelegate>)delegate
+{
+ self = [super init];
+ if (self)
+ {
+ _dataSource = dataSource;
+ _sourceTrackIdx = index;
+ _container = container;
+
+ [self validateSettings];
+
+ _delegate = delegate;
+ }
+
+ return self;
+}
+
+- (void)validateSettings
+{
+ NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
+ self.type = [sourceTrack[keySubTrackType] intValue];
+
+ if (!hb_subtitle_can_burn(_type))
+ {
+ // the source track cannot be burned in, so uncheck the widget
+ self.burnedIn = NO;
+ }
+
+ if (!hb_subtitle_can_force(_type))
+ {
+ // the source track does not support forced flags, so uncheck the widget
+ self.forcedOnly = NO;
+ }
+
+ if (!hb_subtitle_can_pass(self.type, self.container))
+ {
+ self.burnedIn = YES;
+ }
+
+ if (_sourceTrackIdx == 1)
+ {
+ self.forcedOnly = YES;
+ self.burnedIn = YES;
+ }
+
+ // check to see if we are an srt, in which case set our file path and source track type kvp's
+ if (_type == SRTSUB)
+ {
+ self.fileURL = [sourceTrack[keySubTrackSrtFileURL] copy];
+ self.isoLanguage = @"eng";
+ self.charCode = charEncodingArray[CHAR_CODE_DEFAULT_INDEX];
+ }
+ else
+ {
+ self.fileURL = nil;
+ self.isoLanguage = nil;
+ self.charCode = nil;
+ }
+}
+
+#pragma mark - Track properties
+
+- (void)setSourceTrackIdx:(NSUInteger)sourceTrackIdx
+{
+ if (sourceTrackIdx != _sourceTrackIdx)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setSourceTrackIdx:_sourceTrackIdx];
+ }
+
+ NSUInteger oldIdx = _sourceTrackIdx;
+ _sourceTrackIdx = sourceTrackIdx;
+
+ if (!(self.undo.isUndoing || self.undo.isRedoing))
+ {
+ [self validateSettings];
+
+ if (oldIdx != sourceTrackIdx)
+ {
+ [self.delegate track:self didChangeSourceFrom:oldIdx];
+ }
+ }
+}
+
+- (void)setType:(int)type
+{
+ if (type != _type)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setType:_type];
+ }
+ _type = type;
+}
+
+- (void)setForcedOnly:(BOOL)forcedOnly
+{
+ if (forcedOnly != _forcedOnly)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setForcedOnly:_forcedOnly];
+ }
+ _forcedOnly = forcedOnly;
+}
+
+- (void)setBurnedIn:(BOOL)burnedIn
+{
+ if (burnedIn != _burnedIn)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setBurnedIn:_burnedIn];
+ }
+ _burnedIn = burnedIn;
+ if (!(self.undo.isUndoing || self.undo.isRedoing) || !self.validating)
+ {
+ self.validating = YES;
+ if (_burnedIn)
+ {
+ [self.delegate didSetBurnedInOption:self];
+ self.def = NO;
+ };
+ self.validating = NO;
+ }
+}
+
+- (BOOL)validateBurnedIn:(id *)ioValue error:(NSError * __autoreleasing *)outError
+{
+ BOOL retval = YES;
+
+ if (nil != *ioValue)
+ {
+ BOOL value = [*ioValue boolValue];
+ if (value && [self.delegate canSetBurnedInOption:self] == NO)
+ {
+ *ioValue = @NO;
+ }
+ }
+
+ return retval;
+}
+
+- (void)setDef:(BOOL)def
+{
+ if (def != _def)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setDef:_def];
+ }
+ _def = def;
+ if (!(self.undo.isUndoing || self.undo.isRedoing) || !self.validating)
+ {
+ self.validating = YES;
+ if (_def)
+ {
+ [self.delegate didSetDefaultOption:self];
+ self.burnedIn = NO;
+ }
+ self.validating = NO;
+ }
+}
+
+#pragma mark - Srt only properties
+
+- (void)setFileURL:(NSURL *)fileURL
+{
+ if (fileURL != _fileURL)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setFileURL:_fileURL];
+ }
+ _fileURL = [fileURL copy];
+}
+
+- (void)setIsoLanguage:(NSString *)isoLanguage
+{
+ if (![isoLanguage isEqualToString:_isoLanguage])
+ {
+ [[self.undo prepareWithInvocationTarget:self] setIsoLanguage:_isoLanguage];
+ }
+ _isoLanguage = [isoLanguage copy];
+}
+
+- (void)setCharCode:(NSString *)charCode
+{
+ if (![charCode isEqualToString:_charCode])
+ {
+ [[self.undo prepareWithInvocationTarget:self] setCharCode:_charCode];
+ }
+ _charCode = [charCode copy];
+}
+
+- (void)setOffset:(int)offset
+{
+ if (offset != _offset)
+ {
+ [[self.undo prepareWithInvocationTarget:self] setOffset:_def];
+ }
+ _offset = offset;
+}
+
+#pragma mark -
+
+- (NSArray *)sourceTracksArray
+{
+ return [self.dataSource sourceTracksArray];
+}
+
+- (BOOL)isSrt
+{
+ return self.type == SRTSUB;
+}
+
+- (BOOL)isEnabled
+{
+ return self.sourceTrackIdx != 0;
+}
+
+- (BOOL)canPassthru
+{
+ return (BOOL)hb_subtitle_can_pass(self.type, self.container) && self.isEnabled;
+}
+
+- (NSArray<NSString *> *)languages
+{
+ return _languagesArray;
+}
+- (NSArray<NSString *> *)encodings
+{
+ return charEncodingArray;
+}
+
+#pragma mark - KVO
+
++ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
+
+{
+ NSSet *retval = nil;
+
+ if ([key isEqualToString: @"isSrt"])
+ {
+ retval = [NSSet setWithObjects: @"type", nil];
+ }
+ else if ([key isEqualToString: @"isEnabled"])
+ {
+ retval = [NSSet setWithObjects: @"sourceTrackIdx", nil];
+ }
+ else if ([key isEqualToString: @"canPassthru"])
+ {
+ retval = [NSSet setWithObjects: @"isEnabled", @"sourceTrackIdx", nil];
+ }
+
+ return retval;
+}
+
+- (void)setNilValueForKey:(NSString *)key
+{
+ if ([key isEqualToString:@"offset"])
+ {
+ [self setValue:@0 forKey:key];
+ }
+}
+
+#pragma mark - NSCopying
+
+- (instancetype)copyWithZone:(NSZone *)zone
+{
+ HBSubtitlesTrack *copy = [[[self class] alloc] init];
+
+ if (copy)
+ {
+ copy->_sourceTrackIdx = _sourceTrackIdx;
+ copy->_type = _type;
+
+ copy->_forcedOnly = _forcedOnly;
+ copy->_burnedIn = _burnedIn;
+ copy->_def = _def;
+
+ copy->_fileURL = [_fileURL copy];
+ copy->_isoLanguage = [_isoLanguage copy];
+ copy->_charCode = [_charCode copy];
+ copy->_offset = _offset;
+ }
+
+ return copy;
+}
+
+#pragma mark - NSCoding
+
++ (BOOL)supportsSecureCoding
+{
+ return YES;
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ [coder encodeInt:1 forKey:@"HBSubtitlesTrackVersion"];
+
+ encodeInteger(_sourceTrackIdx);
+ encodeInt(_type);
+ encodeInt(_container);
+
+ encodeBool(_forcedOnly);
+ encodeBool(_burnedIn);
+ encodeBool(_def);
+
+ encodeObject(_fileURL);
+ encodeObject(_isoLanguage);
+ encodeObject(_charCode);
+ encodeInt(_offset);
+}
+
+- (instancetype)initWithCoder:(NSCoder *)decoder
+{
+ self = [super init];
+
+ decodeInteger(_sourceTrackIdx);
+ decodeInt(_type);
+ decodeInt(_container);
+
+ decodeBool(_forcedOnly);
+ decodeBool(_burnedIn);
+ decodeBool(_def);
+
+ decodeObject(_fileURL, NSURL);
+ decodeObject(_isoLanguage, NSString);
+ decodeObject(_charCode, NSString);
+ decodeInt(_offset);
+
+ return self;
+}
+
+@end
+
+#pragma mark - Value Trasformers
+
+@implementation HBIsoLanguageTrasformer
+
++ (Class)transformedValueClass
+{
+ return [NSString class];
+}
+
+- (id)transformedValue:(id)value
+{
+ if ([value length])
+ {
+ iso639_lang_t *lang = lang_for_code2([value UTF8String]);
+ if (lang)
+ {
+ return @(lang->eng_name);
+ }
+ }
+ return nil;
+}
+
++ (BOOL)allowsReverseTransformation
+{
+ return YES;
+}
+
+- (id)reverseTransformedValue:(id)value
+{
+ if ([value length])
+ {
+ iso639_lang_t *lang = lang_for_english([value UTF8String]);
+ if (lang)
+ {
+ return @(lang->iso639_2);
+ }
+ }
+ return nil;
+}
+
+@end
diff --git a/macosx/HBTitle.m b/macosx/HBTitle.m
index 0d9f93073..af2fcbe53 100644
--- a/macosx/HBTitle.m
+++ b/macosx/HBTitle.m
@@ -20,19 +20,9 @@ extern NSString *keyAudioInputChannelLayout;
extern NSString *keyAudioTrackLanguageIsoCode;
extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
extern NSString *keySubTrackLanguageIsoCode;
extern NSString *keySubTrackType;
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-
@interface HBTitle ()
@property (nonatomic, readonly) hb_title_t *hb_title;
@@ -224,9 +214,7 @@ extern NSString *keySubTrackSrtCharCode;
/* create a dictionary of source subtitle information to store in our array */
[tracks addObject:@{keySubTrackName: [NSString stringWithFormat:@"%d: %@ (%@) (%@)", i, nativeLanguage, bitmapOrText, subSourceName],
- keySubTrackIndex: @(i),
keySubTrackType: @(subtitle->source),
- keySubTrackLanguage: nativeLanguage,
keySubTrackLanguageIsoCode: @(subtitle->iso639_2)}];
}
diff --git a/macosx/HandBrake Tests/HBMockTitle.m b/macosx/HandBrake Tests/HBMockTitle.m
index 0e61bd414..78ce4c05c 100644
--- a/macosx/HandBrake Tests/HBMockTitle.m
+++ b/macosx/HandBrake Tests/HBMockTitle.m
@@ -18,19 +18,9 @@ extern NSString *keyAudioInputChannelLayout;
extern NSString *keyAudioTrackLanguageIsoCode;
extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
extern NSString *keySubTrackLanguageIsoCode;
extern NSString *keySubTrackType;
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-
@implementation HBMockTitle
- (instancetype)init
@@ -161,7 +151,6 @@ extern NSString *keySubTrackSrtCharCode;
// create a dictionary of source subtitle information to store in our array
[tracks addObject:@{keySubTrackName: [NSString stringWithFormat:@"%d: %@ (%@) (%@)", i, nativeLanguage, bitmapOrText, subSourceName],
- keySubTrackIndex: @(i),
keySubTrackType: @(subtitle->source),
keySubTrackLanguage: nativeLanguage,
keySubTrackLanguageIsoCode: @(subtitle->iso639_2)}];
diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj
index 654be76b1..47f7f3cf1 100644
--- a/macosx/HandBrake.xcodeproj/project.pbxproj
+++ b/macosx/HandBrake.xcodeproj/project.pbxproj
@@ -118,6 +118,7 @@
A9160A351AE7A165009A7818 /* HBCodingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = A9160A341AE7A165009A7818 /* HBCodingUtilities.m */; };
A91726E7197291BC00D1AFEF /* HBChapterTitlesController.m in Sources */ = {isa = PBXBuildFile; fileRef = A91726E6197291BC00D1AFEF /* HBChapterTitlesController.m */; };
A91806711A4807B000FC9BED /* HBRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A91806701A4807B000FC9BED /* HBRange.m */; };
+ A9181CB21BD76F8400E5C8B0 /* HBSubtitlesTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */; };
A91AFD0C1A948827009BECED /* HBOutputFileWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = A91AFD0B1A948827009BECED /* HBOutputFileWriter.m */; };
A91AFD0F1A949472009BECED /* HBJobOutputFileWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = A91AFD0E1A949472009BECED /* HBJobOutputFileWriter.m */; };
A91C024D1A16516A00DEA6F3 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = A91C024C1A16516A00DEA6F3 /* [email protected] */; };
@@ -400,6 +401,8 @@
A91726E6197291BC00D1AFEF /* HBChapterTitlesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBChapterTitlesController.m; sourceTree = "<group>"; };
A918066F1A4807B000FC9BED /* HBRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBRange.h; sourceTree = "<group>"; };
A91806701A4807B000FC9BED /* HBRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBRange.m; sourceTree = "<group>"; };
+ A9181CB01BD76F8400E5C8B0 /* HBSubtitlesTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBSubtitlesTrack.h; sourceTree = "<group>"; };
+ A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBSubtitlesTrack.m; sourceTree = "<group>"; };
A91AFD0A1A948827009BECED /* HBOutputFileWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBOutputFileWriter.h; sourceTree = "<group>"; };
A91AFD0B1A948827009BECED /* HBOutputFileWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBOutputFileWriter.m; sourceTree = "<group>"; };
A91AFD0D1A949472009BECED /* HBJobOutputFileWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBJobOutputFileWriter.h; sourceTree = "<group>"; };
@@ -1022,6 +1025,8 @@
children = (
A91017B21A64440A00039BFB /* HBSubtitles.h */,
A91017B31A64440A00039BFB /* HBSubtitles.m */,
+ A9181CB01BD76F8400E5C8B0 /* HBSubtitlesTrack.h */,
+ A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */,
A9F4728B1976BAA70009EC65 /* HBSubtitlesDefaults.h */,
A9F4728C1976BAA70009EC65 /* HBSubtitlesDefaults.m */,
);
@@ -1510,6 +1515,7 @@
A9DEC87F1A23DF6F00C79B48 /* HBJob.m in Sources */,
A9E2FD271A21BC4A000E8D3F /* HBAddPresetController.m in Sources */,
A975C08E1AE8C5270061870D /* HBStateFormatter.m in Sources */,
+ A9181CB21BD76F8400E5C8B0 /* HBSubtitlesTrack.m in Sources */,
A9D488A51996270300E9B1BA /* HBTreeNode.m in Sources */,
A9597A2A1A49749D00007771 /* HBRange+UIAdditions.m in Sources */,
);