diff options
author | Damiano Galassi <[email protected]> | 2015-10-22 18:52:18 +0200 |
---|---|---|
committer | Damiano Galassi <[email protected]> | 2015-10-22 18:52:18 +0200 |
commit | 449b50737c28a3274abb1200ab54711ce0e659bc (patch) | |
tree | 49ca196e805e741c8b57043d5dd4397890ef8808 | |
parent | 586d58a92e8bd0ba599917e9c65cf913d66a6474 (diff) |
MacGui: rewrote the subtitles tab to use bindings, view-based table view and to support undo/redo.
-rw-r--r-- | macosx/English.lproj/Subtitles.xib | 365 | ||||
-rw-r--r-- | macosx/HBJob+HBJobConversion.m | 135 | ||||
-rw-r--r-- | macosx/HBJob+UIAdditions.m | 15 | ||||
-rw-r--r-- | macosx/HBJob.m | 4 | ||||
-rw-r--r-- | macosx/HBSubtitles.h | 41 | ||||
-rw-r--r-- | macosx/HBSubtitles.m | 509 | ||||
-rw-r--r-- | macosx/HBSubtitlesController.m | 533 | ||||
-rw-r--r-- | macosx/HBSubtitlesTrack.h | 85 | ||||
-rw-r--r-- | macosx/HBSubtitlesTrack.m | 420 | ||||
-rw-r--r-- | macosx/HBTitle.m | 12 | ||||
-rw-r--r-- | macosx/HandBrake Tests/HBMockTitle.m | 11 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/project.pbxproj | 6 |
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 */, ); |