Tag experiments #301
28 changed files with 759 additions and 629 deletions
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21179.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21179.7"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -15,16 +15,16 @@
|
|||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Info Inspector" separatorStyle="none" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" visibleAtLaunch="NO" animationBehavior="utilityWindow" frameAutosaveName="InfoInspector" titlebarAppearsTransparent="YES" id="1" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="582"/>
|
||||
<rect key="contentRect" x="700" y="80" width="300" height="604"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
|
||||
<value key="minSize" type="size" width="240" height="550"/>
|
||||
<value key="minSize" type="size" width="240" height="572"/>
|
||||
<value key="maxSize" type="size" width="400" height="600"/>
|
||||
<view key="contentView" id="2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="582"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="300" height="604"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9">
|
||||
<rect key="frame" x="-2" y="526" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="548" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Artist:" id="10">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -33,7 +33,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11">
|
||||
<rect key="frame" x="-2" y="504" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="526" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album:" id="12">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -42,7 +42,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13">
|
||||
<rect key="frame" x="-2" y="460" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="482" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Track:" id="14">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -51,7 +51,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="15">
|
||||
<rect key="frame" x="-2" y="438" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="460" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Length:" id="16">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -60,16 +60,16 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17">
|
||||
<rect key="frame" x="-2" y="416" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="438" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Year:" id="18">
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Date:" id="18">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19">
|
||||
<rect key="frame" x="-2" y="394" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="416" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Genre:" id="20">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -78,7 +78,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21">
|
||||
<rect key="frame" x="-2" y="350" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="372" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Sample Rate:" id="22">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -87,7 +87,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27">
|
||||
<rect key="frame" x="-2" y="328" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="350" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Channels:" id="28">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -96,7 +96,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="29">
|
||||
<rect key="frame" x="-2" y="306" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="328" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bitrate:" id="32">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -105,7 +105,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="30">
|
||||
<rect key="frame" x="-2" y="284" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="306" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bits Per Sample:" id="31">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -114,7 +114,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23">
|
||||
<rect key="frame" x="-2" y="482" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="504" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Title:" id="24">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -123,7 +123,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="548" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="34">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -135,7 +135,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="35" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="526" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="36">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -147,7 +147,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="504" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="38">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -159,7 +159,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="39" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="482" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="40">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -171,7 +171,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="41" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="460" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="42">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -183,7 +183,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="43" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="438" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="44">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -191,11 +191,11 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.yearText" id="miZ-gp-CqU"/>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.date" id="EUq-4E-Lz3"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="45" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="416" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="46">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -207,7 +207,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="50">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -223,7 +223,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="350" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="52">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -235,7 +235,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="53" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="328" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="54">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -247,7 +247,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="306" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="56">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -259,7 +259,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPg-Mb-Urn">
|
||||
<rect key="frame" x="-2" y="262" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="284" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Codec:" id="cbq-TT-CZX">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -268,7 +268,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ijS-y2-eCZ" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="284" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Yby-OU-cqP">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -280,7 +280,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bti-s6-SIU">
|
||||
<rect key="frame" x="-2" y="240" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="262" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Encoding:" id="8e7-lp-K5l">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -289,7 +289,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L4f-rE-CN3" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="262" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="v14-AG-B9D">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -301,7 +301,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gn9-9b-eV8">
|
||||
<rect key="frame" x="-2" y="218" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="240" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Cuesheet:" id="Cu5-ia-Z5b">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -310,7 +310,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WOl-SC-4tu" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="240" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="UyE-Mc-e39">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -322,7 +322,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="arA-Pj-ANg">
|
||||
<rect key="frame" x="-2" y="196" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="218" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="ReplayGain:" id="qBi-M8-kEx">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -331,7 +331,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2xx-It-i6I" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="218" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="rAo-AP-lVa">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -344,7 +344,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="84">
|
||||
<rect key="frame" x="-2" y="372" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="394" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename:" id="85">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -353,7 +353,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="86" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="372" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="394" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="87">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -365,7 +365,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vB6-9J-5qg">
|
||||
<rect key="frame" x="-2" y="548" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="570" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album Artist:" id="LFJ-QQ-gGr">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -374,7 +374,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cj0-Tw-xpq" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="548" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="570" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="B8w-o8-ZBw">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -386,7 +386,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWx-Sc-Ocv">
|
||||
<rect key="frame" x="-2" y="174" width="107" height="14"/>
|
||||
<rect key="frame" x="-2" y="196" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Play Count:" id="fiv-eh-w3c">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -394,8 +394,17 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cd3-Qt-hCm">
|
||||
<rect key="frame" x="-2" y="174" width="107" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Comment:" id="Ule-N3-dKW">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WSs-wC-mWc" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<rect key="frame" x="113" y="196" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Ial-XI-91y">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -407,6 +416,19 @@
|
|||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ef3-yG-qT1" customClass="ToolTipTextField">
|
||||
<rect key="frame" x="113" y="174" width="170" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="PPV-dt-9Bp">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.comment" id="XzQ-nd-jMU"/>
|
||||
<binding destination="-2" name="value" keyPath="valueToDisplay.comment" id="Esa-PB-mpv"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RWn-fb-0wT">
|
||||
<rect key="frame" x="0.0" y="20" width="300" height="146"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21223.12" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AlbumArtwork" representedClassName="AlbumArtwork" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="artData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="artHash" optional="YES" attributeType="String"/>
|
||||
|
@ -15,10 +15,7 @@
|
|||
<attribute name="title" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="PlaylistEntry" representedClassName="PlaylistEntry" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="album" optional="YES" attributeType="String"/>
|
||||
<attribute name="albumartist" optional="YES" attributeType="String"/>
|
||||
<attribute name="artHash" optional="YES" attributeType="String"/>
|
||||
<attribute name="artist" optional="YES" attributeType="String"/>
|
||||
<attribute name="bitrate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="bitsPerSample" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelConfig" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
@ -30,19 +27,17 @@
|
|||
<attribute name="currentPosition" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="dbIndex" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deLeted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disc" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="encoding" optional="YES" attributeType="String"/>
|
||||
<attribute name="endian" optional="YES" attributeType="String"/>
|
||||
<attribute name="entryId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="error" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="errorMessage" optional="YES" attributeType="String"/>
|
||||
<attribute name="floatingPoint" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="genre" optional="YES" attributeType="String"/>
|
||||
<attribute name="index" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="metadataBlob" optional="YES" attributeType="Transformable"/>
|
||||
<attribute name="metadataLoaded" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="queued" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="queuePosition" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rawTitle" optional="YES" attributeType="String"/>
|
||||
<attribute name="removed" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replayGainAlbumGain" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="replayGainAlbumPeak" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
|
@ -55,12 +50,10 @@
|
|||
<attribute name="spotlightTrack" optional="YES" attributeType="String"/>
|
||||
<attribute name="stopAfter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="totalFrames" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="track" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="trashUrlString" optional="YES" attributeType="String"/>
|
||||
<attribute name="unSigned" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="urlString" optional="YES" attributeType="String"/>
|
||||
<attribute name="volume" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="YES"/>
|
||||
<attribute name="year" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bookmark" optional="YES" attributeType="Binary"/>
|
||||
|
@ -70,7 +63,7 @@
|
|||
<elements>
|
||||
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
|
||||
<element name="PlayCount" positionX="-18" positionY="171" width="128" height="149"/>
|
||||
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="719"/>
|
||||
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="614"/>
|
||||
<element name="SandboxToken" positionX="-18" positionY="171" width="128" height="74"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -494,6 +494,12 @@ static void *playlistControllerContext = &playlistControllerContext;
|
|||
}
|
||||
}
|
||||
|
||||
NSString *cellTextTruncated = cellText;
|
||||
if([cellTextTruncated length] > 1023) {
|
||||
cellTextTruncated = [cellTextTruncated substringToIndex:1023];
|
||||
cellTextTruncated = [cellTextTruncated stringByAppendingString:@"…"];
|
||||
}
|
||||
|
||||
NSView *view = [tableView makeViewWithIdentifier:cellIdentifier owner:nil];
|
||||
if(view && [view isKindOfClass:[NSTableCellView class]]) {
|
||||
NSTableCellView *cellView = (NSTableCellView *)view;
|
||||
|
@ -508,11 +514,11 @@ static void *playlistControllerContext = &playlistControllerContext;
|
|||
NSFont *font = [NSFont monospacedDigitSystemFontOfSize:fontSize weight:NSFontWeightRegular];
|
||||
|
||||
cellView.textField.font = font;
|
||||
cellView.textField.stringValue = cellText;
|
||||
cellView.textField.stringValue = cellTextTruncated;
|
||||
cellView.textField.alignment = cellTextAlignment;
|
||||
|
||||
if(cellView.textField.intrinsicContentSize.width > cellView.textField.frame.size.width - 4)
|
||||
cellView.textField.toolTip = cellText;
|
||||
cellView.textField.toolTip = cellTextTruncated;
|
||||
else
|
||||
cellView.textField.toolTip = [pe statusMessage];
|
||||
|
||||
|
|
|
@ -74,6 +74,23 @@
|
|||
|
||||
@property(nonatomic, readonly) float rating;
|
||||
|
||||
@property(nonatomic) NSString *_Nullable album;
|
||||
@property(nonatomic) NSString *_Nullable albumartist;
|
||||
@property(nonatomic) NSString *_Nullable artist;
|
||||
@property(nonatomic) NSString *_Nullable rawTitle;
|
||||
@property(nonatomic) NSString *_Nullable genre;
|
||||
@property(nonatomic) int32_t disc;
|
||||
@property(nonatomic) int32_t track;
|
||||
@property(nonatomic) int32_t year;
|
||||
|
||||
@property(nonatomic) NSString *_Nullable date;
|
||||
|
||||
@property(nonatomic) NSString *_Nullable comment;
|
||||
|
||||
- (NSString *_Nullable)readAllValuesAsString:(NSString *_Nonnull)tagName;
|
||||
- (void)setValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nullable)value;
|
||||
- (void)addValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nonnull)value;
|
||||
|
||||
- (void)setMetadata:(NSDictionary *_Nonnull)metadata;
|
||||
|
||||
@end
|
||||
|
|
|
@ -503,8 +503,72 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) {
|
|||
self.error = YES;
|
||||
self.errorMessage = NSLocalizedStringFromTableInBundle(@"ErrorMetadata", nil, [NSBundle bundleForClass:[self class]], @"");
|
||||
} else {
|
||||
NSMutableDictionary *metaDict = [[NSMutableDictionary alloc] init];
|
||||
self.volume = 1;
|
||||
[self setValuesForKeysWithDictionary:metadata];
|
||||
for(NSString *key in metadata) {
|
||||
NSString *lowerKey = [key lowercaseString];
|
||||
id valueObj = [metadata objectForKey:key];
|
||||
NSArray *values = nil;
|
||||
NSString *firstValue = nil;
|
||||
NSData *dataValue = nil;
|
||||
if([valueObj isKindOfClass:[NSArray class]]) {
|
||||
values = (NSArray *)valueObj;
|
||||
if([values count]) {
|
||||
firstValue = values[0];
|
||||
}
|
||||
} else if([valueObj isKindOfClass:[NSString class]]) {
|
||||
firstValue = (NSString *)valueObj;
|
||||
values = @[firstValue];
|
||||
} else if([valueObj isKindOfClass:[NSNumber class]]) {
|
||||
NSNumber *numberValue = (NSNumber *)valueObj;
|
||||
firstValue = [numberValue stringValue];
|
||||
values = @[firstValue];
|
||||
} else if([valueObj isKindOfClass:[NSData class]]) {
|
||||
dataValue = (NSData *)valueObj;
|
||||
}
|
||||
if([lowerKey isEqualToString:@"bitrate"]) {
|
||||
self.bitrate = [firstValue intValue];
|
||||
} else if([lowerKey isEqualToString:@"bitspersample"]) {
|
||||
self.bitsPerSample = [firstValue intValue];
|
||||
} else if([lowerKey isEqualToString:@"channelconfig"]) {
|
||||
self.channelConfig = [firstValue intValue];
|
||||
} else if([lowerKey isEqualToString:@"channels"]) {
|
||||
self.channels = [firstValue intValue];
|
||||
} else if([lowerKey isEqualToString:@"codec"]) {
|
||||
self.codec = firstValue;
|
||||
} else if([lowerKey isEqualToString:@"cuesheet"]) {
|
||||
self.cuesheet = firstValue;
|
||||
} else if([lowerKey isEqualToString:@"encoding"]) {
|
||||
self.encoding = firstValue;
|
||||
} else if([lowerKey isEqualToString:@"endian"]) {
|
||||
self.endian = firstValue;
|
||||
} else if([lowerKey isEqualToString:@"floatingpoint"]) {
|
||||
self.floatingPoint = [firstValue boolValue];
|
||||
} else if([lowerKey isEqualToString:@"samplerate"]) {
|
||||
self.sampleRate = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"seekable"]) {
|
||||
self.seekable = [firstValue boolValue];
|
||||
} else if([lowerKey isEqualToString:@"totalframes"]) {
|
||||
self.totalFrames = [firstValue integerValue];
|
||||
} else if([lowerKey isEqualToString:@"unsigned"]) {
|
||||
self.unSigned = [firstValue boolValue];
|
||||
} else if([lowerKey isEqualToString:@"replaygain_album_gain"]) {
|
||||
self.replayGainAlbumGain = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"replaygain_album_peak"]) {
|
||||
self.replayGainAlbumPeak = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"replaygain_track_gain"]) {
|
||||
self.replayGainTrackGain = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"replaygain_track_peak"]) {
|
||||
self.replayGainTrackPeak = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"volume"]) {
|
||||
self.volume = [firstValue floatValue];
|
||||
} else if([lowerKey isEqualToString:@"albumart"]) {
|
||||
self.albumArt = dataValue;
|
||||
} else {
|
||||
[metaDict setObject:values forKey:lowerKey];
|
||||
}
|
||||
}
|
||||
self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDict];
|
||||
}
|
||||
|
||||
[self setMetadataLoaded:YES];
|
||||
|
@ -574,4 +638,221 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) {
|
|||
}
|
||||
}
|
||||
|
||||
@dynamic album;
|
||||
- (NSString *)album {
|
||||
return [self readAllValuesAsString:@"album"];
|
||||
}
|
||||
|
||||
- (void)setAlbum:(NSString *)album {
|
||||
[self setValue:@"album" fromString:album];
|
||||
}
|
||||
|
||||
@dynamic albumartist;
|
||||
- (NSString *)albumartist {
|
||||
NSString *value = [self readAllValuesAsString:@"albumartist"];
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"album artist"];
|
||||
}
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"album_artist"];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setAlbumartist:(NSString *)albumartist {
|
||||
[self setValue:@"albumartist" fromString:albumartist];
|
||||
[self setValue:@"album artist" fromString:nil];
|
||||
[self setValue:@"album_artist" fromString:nil];
|
||||
}
|
||||
|
||||
@dynamic artist;
|
||||
- (NSString *)artist {
|
||||
return [self readAllValuesAsString:@"artist"];
|
||||
}
|
||||
|
||||
- (void)setArtist:(NSString *)artist {
|
||||
[self setValue:@"artist" fromString:artist];
|
||||
}
|
||||
|
||||
@dynamic rawTitle;
|
||||
- (NSString *)rawTitle {
|
||||
return [self readAllValuesAsString:@"title"];
|
||||
}
|
||||
|
||||
- (void)setRawTitle:(NSString *)rawTitle {
|
||||
[self setValue:@"title" fromString:rawTitle];
|
||||
}
|
||||
|
||||
@dynamic genre;
|
||||
- (NSString *)genre {
|
||||
return [self readAllValuesAsString:@"genre"];
|
||||
}
|
||||
|
||||
- (void)setGenre:(NSString *)genre {
|
||||
[self setValue:@"genre" fromString:genre];
|
||||
}
|
||||
|
||||
@dynamic disc;
|
||||
- (int32_t)disc {
|
||||
NSString *value = [self readAllValuesAsString:@"discnumber"];
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"discnum"];
|
||||
}
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"disc"];
|
||||
}
|
||||
if(value) {
|
||||
return [value intValue];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDisc:(int32_t)disc {
|
||||
[self setValue:@"discnumber" fromString:[NSString stringWithFormat:@"%u", disc]];
|
||||
[self setValue:@"discnum" fromString:nil];
|
||||
[self setValue:@"disc" fromString:nil];
|
||||
}
|
||||
|
||||
@dynamic track;
|
||||
- (int32_t)track {
|
||||
NSString *value = [self readAllValuesAsString:@"tracknumber"];
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"tracknum"];
|
||||
}
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"track"];
|
||||
}
|
||||
if(value) {
|
||||
return [value intValue];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@dynamic year;
|
||||
- (int32_t)year {
|
||||
NSString *value = [self readAllValuesAsString:@"date"];
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"recording_date"];
|
||||
}
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"year"];
|
||||
}
|
||||
if(value) {
|
||||
return [value intValue];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setYear:(int32_t)year {
|
||||
NSString *svalue = [NSString stringWithFormat:@"%u", year];
|
||||
[self setValue:@"year" fromString:svalue];
|
||||
[self setValue:@"date" fromString:nil];
|
||||
[self setValue:@"recording_date" fromString:nil];
|
||||
}
|
||||
|
||||
@dynamic date;
|
||||
- (NSString *)date {
|
||||
NSString *value = [self readAllValuesAsString:@"date"];
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"recording_date"];
|
||||
}
|
||||
if(!value) {
|
||||
value = [self readAllValuesAsString:@"year"];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setDate:(NSString *)date {
|
||||
[self setValue:@"date" fromString:date];
|
||||
[self setValue:@"recording_date" fromString:nil];
|
||||
[self setValue:@"year" fromString:nil];
|
||||
}
|
||||
|
||||
@dynamic comment;
|
||||
- (NSString *)comment {
|
||||
return [self readAllValuesAsString:@"comment"];
|
||||
}
|
||||
|
||||
- (void)setComment:(NSString *)comment {
|
||||
[self setValue:@"comment" fromString:comment];
|
||||
}
|
||||
|
||||
- (NSString *_Nullable)readAllValuesAsString:(NSString *_Nonnull)tagName {
|
||||
id metaObj = self.metadataBlob;
|
||||
|
||||
if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *metaDict = (NSDictionary *)metaObj;
|
||||
|
||||
NSArray *values = [metaDict objectForKey:tagName];
|
||||
|
||||
if(values) {
|
||||
return [values componentsJoinedByString:@", "];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)deleteAllValues {
|
||||
self.metadataBlob = nil;
|
||||
}
|
||||
|
||||
- (void)deleteValue:(NSString *_Nonnull)tagName {
|
||||
id metaObj = self.metadataBlob;
|
||||
|
||||
if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *metaDict = (NSDictionary *)metaObj;
|
||||
NSMutableDictionary *metaDictCopy = [metaDict mutableCopy];
|
||||
|
||||
[metaDictCopy removeObjectForKey:tagName];
|
||||
|
||||
self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nullable)value {
|
||||
if(!value) {
|
||||
[self deleteValue:tagName];
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *values = [value componentsSeparatedByString:@", "];
|
||||
|
||||
id metaObj = self.metadataBlob;
|
||||
|
||||
if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *metaDict = (NSDictionary *)metaObj;
|
||||
NSMutableDictionary *metaDictCopy = [metaDict mutableCopy];
|
||||
|
||||
[metaDictCopy setObject:values forKey:tagName];
|
||||
|
||||
self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addValue:(NSString *_Nonnull)tagName fromString:(NSString *_Nonnull)value {
|
||||
id metaObj = self.metadataBlob;
|
||||
|
||||
if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) {
|
||||
NSDictionary *metaDict = (NSDictionary *)metaObj;
|
||||
NSMutableDictionary *metaDictCopy = [metaDict mutableCopy];
|
||||
|
||||
NSArray *values = [metaDictCopy objectForKey:tagName];
|
||||
NSMutableArray *valuesCopy;
|
||||
if(values) {
|
||||
valuesCopy = [values mutableCopy];
|
||||
} else {
|
||||
valuesCopy = [[NSMutableArray alloc] init];
|
||||
}
|
||||
[valuesCopy addObject:value];
|
||||
values = [NSArray arrayWithArray:valuesCopy];
|
||||
[metaDictCopy setObject:values forKey:tagName];
|
||||
|
||||
self.metadataBlob = [NSDictionary dictionaryWithDictionary:metaDictCopy];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -861,6 +861,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
|
|||
}
|
||||
|
||||
- (BOOL)addDataStore {
|
||||
BOOL dataMigrated = [[NSUserDefaults standardUserDefaults] boolForKey:@"metadataMigrated"];
|
||||
|
||||
NSPersistentContainer *pc = playlistController.persistentContainer;
|
||||
if(pc) {
|
||||
NSManagedObjectContext *moc = pc.viewContext;
|
||||
|
@ -902,7 +904,14 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
|
|||
++index;
|
||||
}
|
||||
[resultsCopy removeObjectsAtIndexes:pruneSet];
|
||||
if([pruneSet count]) {
|
||||
|
||||
if(!dataMigrated) {
|
||||
for(PlaylistEntry *pe in resultsCopy) {
|
||||
pe.metadataLoaded = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if([pruneSet count] || !dataMigrated) {
|
||||
[playlistController commitPersistentStore];
|
||||
}
|
||||
|
||||
|
@ -917,6 +926,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
|
|||
[playlistController readQueueFromDataStore];
|
||||
[playlistController readShuffleListFromDataStore];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"metadataMigrated"];
|
||||
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
|
|
|
@ -85,10 +85,10 @@
|
|||
if([[track track] intValue]) [cuesheetMetadata setValue:@([[track track] intValue]) forKey:@"track"];
|
||||
if([track genre]) [cuesheetMetadata setValue:[track genre] forKey:@"genre"];
|
||||
if([[track year] intValue]) [cuesheetMetadata setValue:@([[track year] intValue]) forKey:@"year"];
|
||||
if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replayGainAlbumGain"];
|
||||
if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replayGainAlbumPeak"];
|
||||
if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replayGainTrackGain"];
|
||||
if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replayGainTrackPeak"];
|
||||
if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replaygain_album_gain"];
|
||||
if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replaygain_album_peak"];
|
||||
if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replaygain_track_gain"];
|
||||
if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replaygain_track_peak"];
|
||||
|
||||
return [NSDictionary dictionaryWithDictionary:cuesheetMetadata];
|
||||
}
|
||||
|
|
|
@ -60,23 +60,13 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence);
|
|||
BOOL endOfAudio;
|
||||
|
||||
int metadataIndex;
|
||||
NSString *artist;
|
||||
NSString *albumartist;
|
||||
NSString *album;
|
||||
NSString *title;
|
||||
NSString *genre;
|
||||
NSNumber *year;
|
||||
NSNumber *track;
|
||||
NSNumber *disc;
|
||||
float replayGainAlbumGain;
|
||||
float replayGainAlbumPeak;
|
||||
float replayGainTrackGain;
|
||||
float replayGainTrackPeak;
|
||||
float volumeScale;
|
||||
|
||||
int attachedPicIndex;
|
||||
|
||||
NSData *albumArt;
|
||||
|
||||
NSDictionary *metaDict;
|
||||
|
||||
NSDictionary *id3Metadata;
|
||||
}
|
||||
|
||||
|
|
|
@ -465,21 +465,9 @@ static uint8_t reverse_bits[0x100];
|
|||
|
||||
seekedToStart = !seekable;
|
||||
|
||||
artist = @"";
|
||||
albumartist = @"";
|
||||
album = @"";
|
||||
title = @"";
|
||||
genre = @"";
|
||||
year = @(0);
|
||||
track = @(0);
|
||||
disc = @(0);
|
||||
replayGainAlbumGain = 0.0;
|
||||
replayGainAlbumPeak = 0.0;
|
||||
replayGainTrackGain = 0.0;
|
||||
replayGainTrackPeak = 0.0;
|
||||
volumeScale = 1.0;
|
||||
id3Metadata = [[NSDictionary alloc] init];
|
||||
metaDict = [NSDictionary dictionary];
|
||||
albumArt = [NSData data];
|
||||
id3Metadata = @{};
|
||||
metadataUpdated = NO;
|
||||
[self updateMetadata];
|
||||
|
||||
|
@ -534,21 +522,18 @@ static uint8_t reverse_bits[0x100];
|
|||
[self close];
|
||||
}
|
||||
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
- (void)updateMetadata {
|
||||
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
|
||||
const AVDictionaryEntry *tag = NULL;
|
||||
NSString *_artist = artist;
|
||||
NSString *_albumartist = albumartist;
|
||||
NSString *_album = album;
|
||||
NSString *_title = title;
|
||||
NSString *_genre = genre;
|
||||
NSNumber *_year = year;
|
||||
NSNumber *_track = track;
|
||||
NSNumber *_disc = disc;
|
||||
float _replayGainAlbumGain = replayGainAlbumGain;
|
||||
float _replayGainAlbumPeak = replayGainAlbumPeak;
|
||||
float _replayGainTrackGain = replayGainTrackGain;
|
||||
float _replayGainTrackPeak = replayGainTrackPeak;
|
||||
float _volumeScale = volumeScale;
|
||||
for(size_t i = 0; i < 2; ++i) {
|
||||
AVDictionary *metadata;
|
||||
if(i == 0) {
|
||||
|
@ -567,62 +552,45 @@ static uint8_t reverse_bits[0x100];
|
|||
if(!strcasecmp(tag->key, "streamtitle")) {
|
||||
NSString *artistTitle = guess_encoding_of_string(tag->value);
|
||||
NSArray *splitValues = [artistTitle componentsSeparatedByString:@" - "];
|
||||
_artist = @"";
|
||||
_title = [splitValues objectAtIndex:0];
|
||||
NSString *_artist = @"";
|
||||
NSString *_title = [splitValues objectAtIndex:0];
|
||||
if([splitValues count] > 1) {
|
||||
_artist = _title;
|
||||
_title = [splitValues objectAtIndex:1];
|
||||
setDictionary(_metaDict, @"artist", _artist);
|
||||
setDictionary(_metaDict, @"title", _title);
|
||||
} else {
|
||||
setDictionary(_metaDict, @"title", _title);
|
||||
}
|
||||
} else if(!strcasecmp(tag->key, "icy-url")) {
|
||||
_album = guess_encoding_of_string(tag->value);
|
||||
} else if(!strcasecmp(tag->key, "icy-genre") ||
|
||||
!strcasecmp(tag->key, "genre")) {
|
||||
_genre = guess_encoding_of_string(tag->value);
|
||||
} else if(!strcasecmp(tag->key, "album")) {
|
||||
_album = guess_encoding_of_string(tag->value);
|
||||
} else if(!strcasecmp(tag->key, "album_artist")) {
|
||||
_albumartist = guess_encoding_of_string(tag->value);
|
||||
} else if(!strcasecmp(tag->key, "artist")) {
|
||||
_artist = guess_encoding_of_string(tag->value);
|
||||
setDictionary(_metaDict, @"album", guess_encoding_of_string(tag->value));
|
||||
} else if(!strcasecmp(tag->key, "icy-genre")) {
|
||||
setDictionary(_metaDict, @"genre", guess_encoding_of_string(tag->value));
|
||||
} else if(!strcasecmp(tag->key, "title")) {
|
||||
NSString *_tag = guess_encoding_of_string(tag->value);
|
||||
if(i == 0 && formatCtx->nb_chapters > 1) {
|
||||
_album = _tag;
|
||||
setDictionary(_metaDict, @"album", _tag);
|
||||
} else {
|
||||
_title = _tag;
|
||||
setDictionary(_metaDict, @"title", _tag);
|
||||
}
|
||||
} else if(!strcasecmp(tag->key, "date") ||
|
||||
!strcasecmp(tag->key, "date_recorded")) {
|
||||
NSString *dateString = guess_encoding_of_string(tag->value);
|
||||
_year = @([dateString intValue]);
|
||||
} else if(!strcasecmp(tag->key, "track")) {
|
||||
NSString *trackString = guess_encoding_of_string(tag->value);
|
||||
_track = @([trackString intValue]);
|
||||
} else if(!strcasecmp(tag->key, "disc")) {
|
||||
NSString *discString = guess_encoding_of_string(tag->value);
|
||||
_disc = @([discString intValue]);
|
||||
} else if(!strcasecmp(tag->key, "replaygain_album_gain")) {
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
_replayGainAlbumGain = [rgValue floatValue];
|
||||
} else if(!strcasecmp(tag->key, "replaygain_album_peak")) {
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
_replayGainAlbumPeak = [rgValue floatValue];
|
||||
} else if(!strcasecmp(tag->key, "replaygain_track_gain")) {
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
_replayGainTrackGain = [rgValue floatValue];
|
||||
} else if(!strcasecmp(tag->key, "replaygain_track_peak")) {
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
_replayGainTrackPeak = [rgValue floatValue];
|
||||
} else if(!strcasecmp(tag->key, "date_recorded")) {
|
||||
setDictionary(_metaDict, @"date", guess_encoding_of_string(tag->value));
|
||||
} else if(!strcasecmp(tag->key, "replaygain_gain")) {
|
||||
// global or chapter gain
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
if(i == 0) _replayGainAlbumGain = [rgValue floatValue];
|
||||
else _replayGainTrackGain = [rgValue floatValue];
|
||||
NSString *tagName;
|
||||
if(i == 0)
|
||||
tagName = @"replaygain_album_gain";
|
||||
else
|
||||
tagName = @"replaygain_track_gain";
|
||||
setDictionary(_metaDict, tagName, guess_encoding_of_string(tag->value));
|
||||
} else if(!strcasecmp(tag->key, "replaygain_peak")) {
|
||||
// global or chapter peak
|
||||
NSString *rgValue = guess_encoding_of_string(tag->value);
|
||||
if(i == 0) _replayGainAlbumPeak = [rgValue floatValue];
|
||||
else _replayGainTrackPeak = [rgValue floatValue];
|
||||
NSString *tagName;
|
||||
if(i == 0)
|
||||
tagName = @"replaygain_album_peak";
|
||||
else
|
||||
tagName = @"replaygain_track_peak";
|
||||
setDictionary(_metaDict, tagName, guess_encoding_of_string(tag->value));
|
||||
} else if(!strcasecmp(tag->key, "iTunNORM")) {
|
||||
NSString *tagString = guess_encoding_of_string(tag->value);
|
||||
NSArray *tag = [tagString componentsSeparatedByString:@" "];
|
||||
|
@ -642,8 +610,11 @@ static uint8_t reverse_bits[0x100];
|
|||
float volume1 = -log10((double)(hexvalue1) / 1000) * 10;
|
||||
float volume2 = -log10((double)(hexvalue2) / 1000) * 10;
|
||||
float volumeToUse = MIN(volume1, volume2);
|
||||
_volumeScale = pow(10, volumeToUse / 20);
|
||||
NSNumber *_volumeScale = @(pow(10, volumeToUse / 20));
|
||||
setDictionary(_metaDict, @"volume", [_volumeScale stringValue]);
|
||||
}
|
||||
} else {
|
||||
setDictionary(_metaDict, guess_encoding_of_string(tag->key), guess_encoding_of_string(tag->value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -653,39 +624,28 @@ static uint8_t reverse_bits[0x100];
|
|||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
NSString *_genre = [metadata valueForKey:@"genre"];
|
||||
NSString *_album = [metadata valueForKey:@"album"];
|
||||
NSString *_artist = [metadata valueForKey:@"artist"];
|
||||
NSString *_title = [metadata valueForKey:@"title"];
|
||||
|
||||
if(_genre && [_genre length]) {
|
||||
[_metaDict setObject:@[_genre] forKey:@"genre"];
|
||||
}
|
||||
if(_album && [_album length]) {
|
||||
[_metaDict setObject:@[_album] forKey:@"album"];
|
||||
}
|
||||
if(_artist && [_artist length]) {
|
||||
[_metaDict setObject:@[_artist] forKey:@"artist"];
|
||||
}
|
||||
if(_title && [_title length]) {
|
||||
[_metaDict setObject:@[_title] forKey:@"title"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_artist isEqual:artist] ||
|
||||
![_albumartist isEqual:albumartist] ||
|
||||
![_album isEqual:album] ||
|
||||
![_title isEqual:title] ||
|
||||
![_genre isEqual:genre] ||
|
||||
![_year isEqual:year] ||
|
||||
![_track isEqual:track] ||
|
||||
![_disc isEqual:disc] ||
|
||||
_replayGainAlbumGain != replayGainAlbumGain ||
|
||||
_replayGainAlbumPeak != replayGainAlbumPeak ||
|
||||
_replayGainTrackGain != replayGainTrackGain ||
|
||||
_replayGainTrackPeak != replayGainTrackPeak ||
|
||||
_volumeScale != volumeScale) {
|
||||
artist = _artist;
|
||||
albumartist = _albumartist;
|
||||
album = _album;
|
||||
title = _title;
|
||||
genre = _genre;
|
||||
year = _year;
|
||||
track = _track;
|
||||
disc = _disc;
|
||||
replayGainAlbumGain = _replayGainAlbumGain;
|
||||
replayGainAlbumPeak = _replayGainAlbumPeak;
|
||||
replayGainTrackGain = _replayGainTrackGain;
|
||||
replayGainTrackPeak = _replayGainTrackPeak;
|
||||
volumeScale = _volumeScale;
|
||||
if(![_metaDict isEqualToDictionary:metaDict]) {
|
||||
metaDict = _metaDict;
|
||||
if(![source seekable]) {
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
|
@ -700,7 +660,7 @@ static uint8_t reverse_bits[0x100];
|
|||
Class tagReader = NSClassFromString(@"TagLibID3v2Reader");
|
||||
if(tagReader && [tagReader respondsToSelector:@selector(metadataForTag:)]) {
|
||||
NSDictionary *_id3Metadata = [tagReader metadataForTag:tag];
|
||||
if(![_id3Metadata isEqualTo:id3Metadata]) {
|
||||
if(![_id3Metadata isEqualToDictionary:id3Metadata]) {
|
||||
id3Metadata = _id3Metadata;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
|
@ -710,7 +670,7 @@ static uint8_t reverse_bits[0x100];
|
|||
|
||||
- (void)updateArtwork {
|
||||
NSData *_albumArt = [NSData dataWithBytes:lastReadPacket->data length:lastReadPacket->size];
|
||||
if(![_albumArt isEqual:albumArt]) {
|
||||
if(![_albumArt isEqualToData:albumArt]) {
|
||||
albumArt = _albumArt;
|
||||
if(![source seekable]) {
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
|
@ -1010,7 +970,10 @@ static uint8_t reverse_bits[0x100];
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return [NSDictionary dictionaryByMerging:@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"replayGainAlbumGain": @(replayGainAlbumGain), @"replayGainAlbumPeak": @(replayGainAlbumPeak), @"replayGainTrackGain": @(replayGainTrackGain), @"replayGainTrackPeak": @(replayGainTrackPeak), @"volume": @(volumeScale), @"albumArt": albumArt } with:id3Metadata];
|
||||
NSDictionary *dict1 = @{ @"albumArt": albumArt };
|
||||
NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict];
|
||||
NSDictionary *dict3 = [dict2 dictionaryByMergingWith:id3Metadata];
|
||||
return dict3;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93F040B8FF67A008627D6 /* FlacDecoder.m */; };
|
||||
8301C147287805F500651A6E /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8301C146287805F500651A6E /* NSDictionary+Merge.m */; };
|
||||
836EF0DA27BB970B00BF35B2 /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0D927BB970B00BF35B2 /* libFLAC.8.dylib */; };
|
||||
83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AA660A27B7DAE40098D4B8 /* cuesheet.m */; };
|
||||
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; };
|
||||
|
@ -33,6 +34,8 @@
|
|||
17C93F030B8FF67A008627D6 /* FlacDecoder.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FlacDecoder.h; sourceTree = "<group>"; };
|
||||
17C93F040B8FF67A008627D6 /* FlacDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = FlacDecoder.m; sourceTree = "<group>"; };
|
||||
32DBCF630370AF2F00C91783 /* Flac_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Flac_Prefix.pch; sourceTree = "<group>"; };
|
||||
8301C145287805F500651A6E /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
8301C146287805F500651A6E /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
8356BD1927B3CCBB0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
836EF0D927BB970B00BF35B2 /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = "<group>"; };
|
||||
83747C4A2862DCF40021245F /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
|
||||
|
@ -89,6 +92,8 @@
|
|||
08FB77AFFE84173DC02AAC07 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8301C145287805F500651A6E /* NSDictionary+Merge.h */,
|
||||
8301C146287805F500651A6E /* NSDictionary+Merge.m */,
|
||||
83AA660A27B7DAE40098D4B8 /* cuesheet.m */,
|
||||
8356BD1927B3CCBB0074E50C /* HTTPSource.h */,
|
||||
8384912D180816C900E7332D /* Logging.h */,
|
||||
|
@ -211,6 +216,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8301C147287805F500651A6E /* NSDictionary+Merge.m in Sources */,
|
||||
17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */,
|
||||
83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */,
|
||||
);
|
||||
|
|
|
@ -36,18 +36,8 @@
|
|||
BOOL streamOpened;
|
||||
BOOL abortFlag;
|
||||
|
||||
NSString *artist;
|
||||
NSString *albumartist;
|
||||
NSString *album;
|
||||
NSString *title;
|
||||
NSString *genre;
|
||||
NSNumber *year;
|
||||
NSNumber *track;
|
||||
NSNumber *disc;
|
||||
float replayGainAlbumGain;
|
||||
float replayGainAlbumPeak;
|
||||
float replayGainTrackGain;
|
||||
float replayGainTrackPeak;
|
||||
NSDictionary *metaDict;
|
||||
NSDictionary *icyMetaDict;
|
||||
|
||||
NSData *albumArt;
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#import "HTTPSource.h"
|
||||
|
||||
#import "NSDictionary+Merge.h"
|
||||
|
||||
extern void grabbag__cuesheet_emit(NSString **out, const FLAC__StreamMetadata *cuesheet, const char *file_reference);
|
||||
|
||||
@implementation FlacDecoder
|
||||
|
@ -178,6 +180,15 @@ FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder,
|
|||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
// This callback is only called for STREAMINFO blocks
|
||||
void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
|
||||
// Some flacs observed in the wild have multiple STREAMINFO metadata blocks,
|
||||
|
@ -223,18 +234,7 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta
|
|||
}
|
||||
|
||||
if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
|
||||
NSString *_artist = flacDecoder->artist;
|
||||
NSString *_albumartist = flacDecoder->albumartist;
|
||||
NSString *_album = flacDecoder->album;
|
||||
NSString *_title = flacDecoder->title;
|
||||
NSString *_genre = flacDecoder->genre;
|
||||
NSNumber *_year = flacDecoder->year;
|
||||
NSNumber *_track = flacDecoder->track;
|
||||
NSNumber *_disc = flacDecoder->disc;
|
||||
float _replayGainAlbumGain = flacDecoder->replayGainAlbumGain;
|
||||
float _replayGainAlbumPeak = flacDecoder->replayGainAlbumPeak;
|
||||
float _replayGainTrackGain = flacDecoder->replayGainTrackGain;
|
||||
float _replayGainTrackPeak = flacDecoder->replayGainTrackPeak;
|
||||
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
|
||||
NSString *_cuesheet = flacDecoder->cuesheet;
|
||||
const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment;
|
||||
for(int i = 0; i < vorbis_comment->num_comments; ++i) {
|
||||
|
@ -246,74 +246,23 @@ void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMeta
|
|||
free(_name);
|
||||
free(_value);
|
||||
name = [name lowercaseString];
|
||||
if([name isEqualToString:@"artist"]) {
|
||||
_artist = value;
|
||||
} else if([name isEqualToString:@"albumartist"]) {
|
||||
_albumartist = value;
|
||||
} else if([name isEqualToString:@"album"]) {
|
||||
_album = value;
|
||||
} else if([name isEqualToString:@"title"]) {
|
||||
_title = value;
|
||||
} else if([name isEqualToString:@"genre"]) {
|
||||
_genre = value;
|
||||
} else if([name isEqualToString:@"cuesheet"]) {
|
||||
if([name isEqualToString:@"cuesheet"]) {
|
||||
_cuesheet = value;
|
||||
flacDecoder->cuesheetFound = YES;
|
||||
} else if([name isEqualToString:@"date"] ||
|
||||
[name isEqualToString:@"year"]) {
|
||||
_year = @([value intValue]);
|
||||
} else if([name isEqualToString:@"tracknumber"] ||
|
||||
[name isEqualToString:@"tracknum"] ||
|
||||
[name isEqualToString:@"track"]) {
|
||||
_track = @([value intValue]);
|
||||
} else if([name isEqualToString:@"discnumber"] ||
|
||||
[name isEqualToString:@"discnum"] ||
|
||||
[name isEqualToString:@"disc"]) {
|
||||
_disc = @([value intValue]);
|
||||
} else if([name isEqualToString:@"replaygain_album_gain"]) {
|
||||
_replayGainAlbumGain = [value floatValue];
|
||||
} else if([name isEqualToString:@"replaygain_album_peak"]) {
|
||||
_replayGainAlbumPeak = [value floatValue];
|
||||
} else if([name isEqualToString:@"replaygain_track_gain"]) {
|
||||
_replayGainTrackGain = [value floatValue];
|
||||
} else if([name isEqualToString:@"replaygain_track_peak"]) {
|
||||
_replayGainTrackPeak = [value floatValue];
|
||||
} else if([name isEqualToString:@"waveformatextensible_channel_mask"]) {
|
||||
if([value hasPrefix:@"0x"]) {
|
||||
char *end;
|
||||
const char *_value = [value UTF8String] + 2;
|
||||
flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16);
|
||||
}
|
||||
} else {
|
||||
setDictionary(_metaDict, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_artist isEqual:flacDecoder->artist] ||
|
||||
![_albumartist isEqual:flacDecoder->albumartist] ||
|
||||
![_album isEqual:flacDecoder->album] ||
|
||||
![_title isEqual:flacDecoder->title] ||
|
||||
![_genre isEqual:flacDecoder->genre] ||
|
||||
![_cuesheet isEqual:flacDecoder->cuesheet] ||
|
||||
![_year isEqual:flacDecoder->year] ||
|
||||
![_track isEqual:flacDecoder->track] ||
|
||||
![_disc isEqual:flacDecoder->disc] ||
|
||||
_replayGainAlbumGain != flacDecoder->replayGainAlbumGain ||
|
||||
_replayGainAlbumPeak != flacDecoder->replayGainAlbumPeak ||
|
||||
_replayGainTrackGain != flacDecoder->replayGainTrackGain ||
|
||||
_replayGainTrackPeak != flacDecoder->replayGainTrackPeak) {
|
||||
flacDecoder->artist = _artist;
|
||||
flacDecoder->albumartist = _albumartist;
|
||||
flacDecoder->album = _album;
|
||||
flacDecoder->title = _title;
|
||||
flacDecoder->genre = _genre;
|
||||
flacDecoder->cuesheet = _cuesheet;
|
||||
flacDecoder->year = _year;
|
||||
flacDecoder->track = _track;
|
||||
flacDecoder->disc = _disc;
|
||||
flacDecoder->replayGainAlbumGain = _replayGainAlbumGain;
|
||||
flacDecoder->replayGainAlbumPeak = _replayGainAlbumPeak;
|
||||
flacDecoder->replayGainTrackGain = _replayGainTrackGain;
|
||||
flacDecoder->replayGainTrackPeak = _replayGainTrackPeak;
|
||||
if(![_metaDict isEqualToDictionary:flacDecoder->metaDict]) {
|
||||
flacDecoder->metaDict = _metaDict;
|
||||
|
||||
if(![flacDecoder->source seekable]) {
|
||||
[flacDecoder willChangeValueForKey:@"metadata"];
|
||||
|
@ -348,18 +297,8 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
isOggFlac = YES;
|
||||
}
|
||||
|
||||
artist = @"";
|
||||
albumartist = @"";
|
||||
album = @"";
|
||||
title = @"";
|
||||
genre = @"";
|
||||
year = @(0);
|
||||
track = @(0);
|
||||
disc = @(0);
|
||||
replayGainAlbumGain = 0.0;
|
||||
replayGainAlbumPeak = 0.0;
|
||||
replayGainTrackGain = 0.0;
|
||||
replayGainTrackPeak = 0.0;
|
||||
metaDict = [NSDictionary dictionary];
|
||||
icyMetaDict = [NSDictionary dictionary];
|
||||
albumArt = [NSData data];
|
||||
cuesheetFound = NO;
|
||||
cuesheet = @"";
|
||||
|
@ -461,20 +400,28 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
NSString *_genre = [metadata valueForKey:@"genre"];
|
||||
NSString *_album = [metadata valueForKey:@"album"];
|
||||
NSString *_artist = [metadata valueForKey:@"artist"];
|
||||
NSString *_title = [metadata valueForKey:@"title"];
|
||||
if(![_genre isEqualToString:genre] ||
|
||||
![_album isEqualToString:album] ||
|
||||
![_artist isEqualToString:artist] ||
|
||||
![_title isEqualToString:title]) {
|
||||
genre = _genre;
|
||||
album = _album;
|
||||
artist = _artist;
|
||||
title = _title;
|
||||
|
||||
if(_genre && [_genre length]) {
|
||||
setDictionary(_icyMetaDict, @"genre", _genre);
|
||||
}
|
||||
if(_album && [_album length]) {
|
||||
setDictionary(_icyMetaDict, @"album", _album);
|
||||
}
|
||||
if(_artist && [_artist length]) {
|
||||
setDictionary(_icyMetaDict, @"artist", _artist);
|
||||
}
|
||||
if(_title && [_title length]) {
|
||||
setDictionary(_icyMetaDict, @"title", _title);
|
||||
}
|
||||
if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
|
||||
icyMetaDict = _icyMetaDict;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
|
@ -557,7 +504,10 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return @{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"replayGainAlbumGain": @(replayGainAlbumGain), @"replayGainAlbumPeak": @(replayGainAlbumPeak), @"replayGainTrackGain": @(replayGainTrackGain), @"replayGainTrackPeak": @(replayGainTrackPeak), @"cuesheet": cuesheet, @"albumArt": albumArt };
|
||||
NSDictionary *dict1 = @{ @"albumArt": albumArt, @"cuesheet": cuesheet };
|
||||
NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict];
|
||||
NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict];
|
||||
return dict3;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -24,12 +24,6 @@
|
|||
int tagLengthMs;
|
||||
int tagFadeMs;
|
||||
|
||||
float replayGainAlbumGain;
|
||||
float replayGainAlbumPeak;
|
||||
float replayGainTrackGain;
|
||||
float replayGainTrackPeak;
|
||||
float volume;
|
||||
|
||||
int type;
|
||||
|
||||
int sampleRate;
|
||||
|
|
|
@ -220,6 +220,20 @@ static int parse_time_crap(NSString *value) {
|
|||
return totalSeconds;
|
||||
}
|
||||
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
if([array count]) {
|
||||
NSString *existing = array[0];
|
||||
array[0] = [existing stringByAppendingFormat:@"\r\n%@", value];
|
||||
} else {
|
||||
[array addObject:value];
|
||||
}
|
||||
}
|
||||
|
||||
static int psf_info_meta(void *context, const char *name, const char *value) {
|
||||
struct psf_info_meta_state *state = (struct psf_info_meta_state *)context;
|
||||
|
||||
|
@ -232,46 +246,18 @@ static int psf_info_meta(void *context, const char *name, const char *value) {
|
|||
|
||||
if([taglc isEqualToString:@"game"]) {
|
||||
taglc = @"album";
|
||||
} else if([taglc isEqualToString:@"date"]) {
|
||||
taglc = @"year";
|
||||
} else if([taglc isEqualToString:@"album artist"]) {
|
||||
taglc = @"albumartist";
|
||||
} else if([taglc isEqualToString:@"tracknumber"]) {
|
||||
taglc = @"track";
|
||||
} else if([taglc isEqualToString:@"discnumber"]) {
|
||||
taglc = @"disc";
|
||||
}
|
||||
|
||||
if([taglc hasPrefix:@"replaygain_"]) {
|
||||
if([taglc hasPrefix:@"replaygain_album_"]) {
|
||||
if([taglc hasSuffix:@"gain"])
|
||||
state->albumGain = [svalue floatValue];
|
||||
else if([taglc hasSuffix:@"peak"])
|
||||
state->albumPeak = [svalue floatValue];
|
||||
} else if([taglc hasPrefix:@"replaygain_track_"]) {
|
||||
if([taglc hasSuffix:@"gain"])
|
||||
state->trackGain = [svalue floatValue];
|
||||
else if([taglc hasSuffix:@"peak"])
|
||||
state->trackPeak = [svalue floatValue];
|
||||
}
|
||||
} else if([taglc isEqualToString:@"volume"]) {
|
||||
state->volume = [svalue floatValue];
|
||||
} else if([taglc isEqualToString:@"length"]) {
|
||||
if([taglc isEqualToString:@"length"]) {
|
||||
state->tag_length_ms = parse_time_crap(svalue);
|
||||
setDictionary(state->info, @"psf_length", svalue);
|
||||
} else if([taglc isEqualToString:@"fade"]) {
|
||||
state->tag_fade_ms = parse_time_crap(svalue);
|
||||
setDictionary(state->info, @"psf_fade", svalue);
|
||||
} else if([taglc isEqualToString:@"utf8"]) {
|
||||
state->utf8 = true;
|
||||
} else if([taglc isEqualToString:@"title"] ||
|
||||
[taglc isEqualToString:@"albumartist"] ||
|
||||
[taglc isEqualToString:@"artist"] ||
|
||||
[taglc isEqualToString:@"album"] ||
|
||||
[taglc isEqualToString:@"genre"]) {
|
||||
[state->info setObject:svalue forKey:taglc];
|
||||
} else if([taglc isEqualToString:@"year"] ||
|
||||
[taglc isEqualToString:@"track"] ||
|
||||
[taglc isEqualToString:@"disc"]) {
|
||||
[state->info setObject:@([svalue intValue]) forKey:taglc];
|
||||
} else {
|
||||
setDictionary(state->info, taglc, svalue);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1218,12 +1204,6 @@ static int usf_info(void *context, const char *name, const char *value) {
|
|||
tagFadeMs = (int)ceil(defaultFade * 1000.0);
|
||||
}
|
||||
|
||||
replayGainAlbumGain = info.albumGain;
|
||||
replayGainAlbumPeak = info.albumPeak;
|
||||
replayGainTrackGain = info.trackGain;
|
||||
replayGainTrackPeak = info.trackPeak;
|
||||
volume = info.volume;
|
||||
|
||||
metadataList = info.info;
|
||||
|
||||
framesLength = [self retrieveFrameCount:tagLengthMs];
|
||||
|
@ -1573,11 +1553,6 @@ static int usf_info(void *context, const char *name, const char *value) {
|
|||
@"totalFrames": @(totalFrames),
|
||||
@"bitrate": @(0),
|
||||
@"seekable": @(YES),
|
||||
@"replayGainAlbumGain": @(replayGainAlbumGain),
|
||||
@"replayGainAlbumPeak": @(replayGainAlbumPeak),
|
||||
@"replayGainTrackGain": @(replayGainTrackGain),
|
||||
@"replayGainTrackPeak": @(replayGainTrackPeak),
|
||||
@"volume": @(volume),
|
||||
@"codec": codec,
|
||||
@"endian": @"host",
|
||||
@"encoding": @"synthesized" };
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
return 1.0f;
|
||||
}
|
||||
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url {
|
||||
id audioSourceClass = NSClassFromString(@"AudioSource");
|
||||
id<CogSource> source = [audioSourceClass audioSourceForURL:url];
|
||||
|
@ -62,9 +71,7 @@
|
|||
midi_meta_data_item item;
|
||||
bool remap_display_name = !metadata.get_item("title", item);
|
||||
|
||||
NSArray *allowedKeys = @[@"title", @"artist", @"albumartist", @"album", @"genre"];
|
||||
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:10];
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
for(size_t i = 0; i < metadata.get_count(); ++i) {
|
||||
const midi_meta_data_item &item = metadata[i];
|
||||
|
@ -72,11 +79,7 @@
|
|||
if(![name isEqualToString:@"type"]) {
|
||||
if(remap_display_name && [name isEqualToString:@"display_name"])
|
||||
name = @"title";
|
||||
if([allowedKeys containsObject:name]) {
|
||||
[dict setObject:guess_encoding_of_string(item.m_value.c_str()) forKey:name];
|
||||
} else if([name isEqualToString:@"year"]) {
|
||||
[dict setObject:@([guess_encoding_of_string(item.m_value.c_str()) intValue]) forKey:name];
|
||||
}
|
||||
setDictionary(dict, name, guess_encoding_of_string(item.m_value.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,15 @@
|
|||
return 1.0f;
|
||||
}
|
||||
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)metadataForURL:(NSURL *)url {
|
||||
id audioSourceClass = NSClassFromString(@"AudioSource");
|
||||
id<CogSource> source = [audioSourceClass audioSourceForURL:url];
|
||||
|
@ -55,41 +64,25 @@
|
|||
std::map<std::string, std::string> ctls;
|
||||
openmpt::module *mod = new openmpt::module(data, std::clog, ctls);
|
||||
|
||||
NSString *title = nil;
|
||||
NSString *artist = nil;
|
||||
// NSString * comment = nil;
|
||||
NSString *date = nil;
|
||||
NSString *type = nil;
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
std::vector<std::string> keys = mod->get_metadata_keys();
|
||||
|
||||
for(std::vector<std::string>::iterator key = keys.begin(); key != keys.end(); ++key) {
|
||||
if(*key == "title")
|
||||
title = guess_encoding_of_string(mod->get_metadata(*key).c_str());
|
||||
else if(*key == "artist")
|
||||
artist = guess_encoding_of_string(mod->get_metadata(*key).c_str());
|
||||
/*else if ( *key == "message" )
|
||||
comment = guess_encoding_of_string(mod->get_metadata( *key ).c_str());*/
|
||||
else if(*key == "date")
|
||||
date = guess_encoding_of_string(mod->get_metadata(*key).c_str());
|
||||
else if(*key == "type_long")
|
||||
type = guess_encoding_of_string(mod->get_metadata(*key).c_str());
|
||||
NSString *tag = guess_encoding_of_string((*key).c_str());
|
||||
NSString *value = guess_encoding_of_string(mod->get_metadata(*key).c_str());
|
||||
if(*key == "type")
|
||||
continue;
|
||||
else if(*key == "type_long") {
|
||||
setDictionary(dict, @"codec", value);
|
||||
} else {
|
||||
setDictionary(dict, tag, value);
|
||||
}
|
||||
}
|
||||
|
||||
delete mod;
|
||||
|
||||
if(title == nil)
|
||||
title = @"";
|
||||
if(artist == nil)
|
||||
artist = @"";
|
||||
/*if (comment == nil)
|
||||
comment = @"";*/
|
||||
if(date == nil)
|
||||
date = @"";
|
||||
if(type == nil)
|
||||
type = @"";
|
||||
|
||||
return @{ @"title": title, @"artist": artist, /*@"comment": comment,*/ @"year": @([date intValue]), @"codec": type };
|
||||
return dict;
|
||||
} catch(std::exception & /*e*/) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -23,23 +23,13 @@
|
|||
int channels;
|
||||
long totalFrames;
|
||||
|
||||
NSString *artist;
|
||||
NSString *albumartist;
|
||||
NSString *album;
|
||||
NSString *title;
|
||||
NSString *genre;
|
||||
NSNumber *year;
|
||||
NSNumber *track;
|
||||
NSNumber *disc;
|
||||
float replayGainAlbumGain;
|
||||
float replayGainTrackGain;
|
||||
|
||||
NSString *icygenre;
|
||||
NSString *icyalbum;
|
||||
NSString *icyartist;
|
||||
NSString *icytitle;
|
||||
NSDictionary *metaDict;
|
||||
NSDictionary *icyMetaDict;
|
||||
|
||||
NSData *albumArt;
|
||||
|
||||
float replayGainAlbumGain;
|
||||
float replayGainTrackGain;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#import "NSDictionary+Merge.h"
|
||||
|
||||
#import <FLAC/metadata.h>
|
||||
|
||||
@implementation OpusFile
|
||||
|
||||
static const int MAXCHANNELS = 8;
|
||||
|
@ -121,113 +123,60 @@ opus_int64 sourceTell(void *_stream) {
|
|||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
artist = @"";
|
||||
albumartist = @"";
|
||||
album = @"";
|
||||
title = @"";
|
||||
genre = @"";
|
||||
icygenre = @"";
|
||||
icyalbum = @"";
|
||||
icyartist = @"";
|
||||
icytitle = @"";
|
||||
year = @(0);
|
||||
track = @(0);
|
||||
disc = @(0);
|
||||
metaDict = [NSDictionary dictionary];
|
||||
icyMetaDict = [NSDictionary dictionary];
|
||||
albumArt = [NSData data];
|
||||
|
||||
[self updateMetadata];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)parseTag:(NSString *)tag fromTags:(const OpusTags *)tags {
|
||||
NSMutableArray *tagStrings = [[NSMutableArray alloc] init];
|
||||
|
||||
int tagCount = opus_tags_query_count(tags, [tag UTF8String]);
|
||||
|
||||
for(int i = 0; i < tagCount; ++i) {
|
||||
const char *value = opus_tags_query(tags, [tag UTF8String], i);
|
||||
[tagStrings addObject:guess_encoding_of_string(value)];
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
|
||||
return [tagStrings componentsJoinedByString:@", "];
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
- (void)updateMetadata {
|
||||
const OpusTags *tags = op_tags(opusRef, -1);
|
||||
const struct OpusTags *tags = op_tags(opusRef, -1);
|
||||
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
|
||||
NSData *_albumArt = albumArt;
|
||||
|
||||
if(tags) {
|
||||
NSString *_artist = [self parseTag:@"artist" fromTags:tags];
|
||||
NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags];
|
||||
NSString *_album = [self parseTag:@"album" fromTags:tags];
|
||||
NSString *_title = [self parseTag:@"title" fromTags:tags];
|
||||
NSString *_genre = [self parseTag:@"genre" fromTags:tags];
|
||||
for(int i = 0; i < tags->comments; ++i) {
|
||||
FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] };
|
||||
char *name, *value;
|
||||
if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) {
|
||||
NSString *tagName = guess_encoding_of_string(name);
|
||||
NSString *tagValue = guess_encoding_of_string(value);
|
||||
free(name);
|
||||
free(value);
|
||||
|
||||
NSString *_yearDate = [self parseTag:@"date" fromTags:tags];
|
||||
NSString *_yearYear = [self parseTag:@"year" fromTags:tags];
|
||||
tagName = [tagName lowercaseString];
|
||||
|
||||
NSNumber *_year = @(0);
|
||||
if([_yearDate length])
|
||||
_year = @([_yearDate intValue]);
|
||||
else if([_yearYear length])
|
||||
_year = @([_yearYear intValue]);
|
||||
|
||||
NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags];
|
||||
NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags];
|
||||
NSString *_trackTrack = [self parseTag:@"track" fromTags:tags];
|
||||
|
||||
NSNumber *_track = @(0);
|
||||
if([_trackNumber length])
|
||||
_track = @([_trackNumber intValue]);
|
||||
else if([_trackNum length])
|
||||
_track = @([_trackNum intValue]);
|
||||
else if([_trackTrack length])
|
||||
_track = @([_trackTrack intValue]);
|
||||
|
||||
NSString *_discNumber = [self parseTag:@"discnumber" fromTags:tags];
|
||||
NSString *_discNum = [self parseTag:@"discnum" fromTags:tags];
|
||||
NSString *_discDisc = [self parseTag:@"disc" fromTags:tags];
|
||||
|
||||
NSNumber *_disc = @(0);
|
||||
if([_discNumber length])
|
||||
_disc = @([_discNumber intValue]);
|
||||
else if([_discNum length])
|
||||
_disc = @([_discNum intValue]);
|
||||
else if([_discDisc length])
|
||||
_disc = @([_discDisc intValue]);
|
||||
|
||||
NSData *_albumArt = [NSData data];
|
||||
|
||||
size_t count = opus_tags_query_count(tags, "METADATA_BLOCK_PICTURE");
|
||||
if(count) {
|
||||
const char *pictureTag = opus_tags_query(tags, "METADATA_BLOCK_PICTURE", 0);
|
||||
OpusPictureTag _pic = { 0 };
|
||||
if(opus_picture_tag_parse(&_pic, pictureTag) >= 0) {
|
||||
if(_pic.format == OP_PIC_FORMAT_PNG ||
|
||||
_pic.format == OP_PIC_FORMAT_JPEG ||
|
||||
_pic.format == OP_PIC_FORMAT_GIF) {
|
||||
_albumArt = [NSData dataWithBytes:_pic.data length:_pic.data_length];
|
||||
if([tagName isEqualToString:@"metadata_block_picture"]) {
|
||||
OpusPictureTag _pic = { 0 };
|
||||
if(opus_picture_tag_parse(&_pic, [tagValue UTF8String]) >= 0) {
|
||||
if(_pic.format == OP_PIC_FORMAT_PNG ||
|
||||
_pic.format == OP_PIC_FORMAT_JPEG ||
|
||||
_pic.format == OP_PIC_FORMAT_GIF) {
|
||||
_albumArt = [NSData dataWithBytes:_pic.data length:_pic.data_length];
|
||||
}
|
||||
opus_picture_tag_clear(&_pic);
|
||||
}
|
||||
} else {
|
||||
setDictionary(_metaDict, tagName, tagValue);
|
||||
}
|
||||
opus_picture_tag_clear(&_pic);
|
||||
}
|
||||
}
|
||||
|
||||
if(![_artist isEqual:artist] ||
|
||||
![_albumartist isEqual:albumartist] ||
|
||||
![_album isEqual:album] ||
|
||||
![_title isEqual:title] ||
|
||||
![_genre isEqual:genre] ||
|
||||
![_year isEqual:year] ||
|
||||
![_track isEqual:year] ||
|
||||
![_disc isEqual:disc] ||
|
||||
![_albumArt isEqual:albumArt]) {
|
||||
artist = _artist;
|
||||
albumartist = _albumartist;
|
||||
album = _album;
|
||||
title = _title;
|
||||
genre = _genre;
|
||||
year = _year;
|
||||
track = _track;
|
||||
disc = _disc;
|
||||
if(![_albumArt isEqualToData:albumArt] ||
|
||||
![_metaDict isEqualToDictionary:metaDict]) {
|
||||
metaDict = _metaDict;
|
||||
albumArt = _albumArt;
|
||||
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
|
@ -239,31 +188,35 @@ opus_int64 sourceTell(void *_stream) {
|
|||
- (void)updateIcyMetadata {
|
||||
if([source seekable]) return;
|
||||
|
||||
NSString *_genre = icygenre;
|
||||
NSString *_album = icyalbum;
|
||||
NSString *_artist = icyartist;
|
||||
NSString *_title = icytitle;
|
||||
NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
NSString *_genre = [metadata valueForKey:@"genre"];
|
||||
NSString *_album = [metadata valueForKey:@"album"];
|
||||
NSString *_artist = [metadata valueForKey:@"artist"];
|
||||
NSString *_title = [metadata valueForKey:@"title"];
|
||||
|
||||
if(_genre && [_genre length]) {
|
||||
setDictionary(_icyMetaDict, @"genre", _genre);
|
||||
}
|
||||
if(_album && [_album length]) {
|
||||
setDictionary(_icyMetaDict, @"album", _album);
|
||||
}
|
||||
if(_artist && [_artist length]) {
|
||||
setDictionary(_icyMetaDict, @"artist", _artist);
|
||||
}
|
||||
if(_title && [_title length]) {
|
||||
setDictionary(_icyMetaDict, @"title", _title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:icygenre] ||
|
||||
![_album isEqual:icyalbum] ||
|
||||
![_artist isEqual:icyartist] ||
|
||||
![_title isEqual:icytitle]) {
|
||||
icygenre = _genre;
|
||||
icyalbum = _album;
|
||||
icyartist = _artist;
|
||||
icytitle = _title;
|
||||
if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
|
||||
icyMetaDict = _icyMetaDict;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
|
@ -338,15 +291,18 @@ opus_int64 sourceTell(void *_stream) {
|
|||
@"totalFrames": @(totalFrames),
|
||||
@"bitrate": @(bitrate),
|
||||
@"seekable": @([source seekable] && seekable),
|
||||
@"replayGainAlbumGain": @(replayGainAlbumGain),
|
||||
@"replayGainTrackGain": @(replayGainTrackGain),
|
||||
@"replaygain_album_gain": @(replayGainAlbumGain),
|
||||
@"replaygain_track_gain": @(replayGainTrackGain),
|
||||
@"codec": @"Opus",
|
||||
@"endian": @"host",
|
||||
@"encoding": @"lossy" };
|
||||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return [@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"albumArt": albumArt } dictionaryByMergingWith:@{ @"genre": icygenre, @"album": icyalbum, @"artist": icyartist, @"title": icytitle }];
|
||||
NSDictionary *dict1 = @{ @"albumArt": albumArt };
|
||||
NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict];
|
||||
NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict];
|
||||
return dict3;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8301C14928780C1A00651A6E /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8301C14828780C1A00651A6E /* libFLAC.8.dylib */; };
|
||||
83186316285CEC91001422CC /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83186315285CEC91001422CC /* NSDictionary+Merge.m */; };
|
||||
836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */; };
|
||||
8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8375B03F17FFEA400092A79F /* Cocoa.framework */; };
|
||||
|
@ -27,6 +28,7 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8301C14828780C1A00651A6E /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = "<group>"; };
|
||||
83186314285CEC91001422CC /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
83186315285CEC91001422CC /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
833F68411CDBCABC00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
|
@ -53,6 +55,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */,
|
||||
8301C14928780C1A00651A6E /* libFLAC.8.dylib in Frameworks */,
|
||||
836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -90,6 +93,7 @@
|
|||
8375B03E17FFEA400092A79F /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8301C14828780C1A00651A6E /* libFLAC.8.dylib */,
|
||||
836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */,
|
||||
8375B03F17FFEA400092A79F /* Cocoa.framework */,
|
||||
8375B04117FFEA400092A79F /* Other Frameworks */,
|
||||
|
@ -340,10 +344,14 @@
|
|||
../../ThirdParty/opusfile/include,
|
||||
../../ThirdParty/opus/include,
|
||||
../../ThirdParty/ogg/include,
|
||||
../../ThirdParty/flac/include,
|
||||
);
|
||||
INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = ../../ThirdParty/opusfile/lib;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
../../ThirdParty/opusfile/lib,
|
||||
../../ThirdParty/flac/lib,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.kode54.opus;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
|
@ -364,10 +372,14 @@
|
|||
../../ThirdParty/opusfile/include,
|
||||
../../ThirdParty/opus/include,
|
||||
../../ThirdParty/ogg/include,
|
||||
../../ThirdParty/flac/include,
|
||||
);
|
||||
INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = ../../ThirdParty/opusfile/lib;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
../../ThirdParty/opusfile/lib,
|
||||
../../ThirdParty/flac/lib,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.kode54.opus;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
|
|
|
@ -87,10 +87,10 @@
|
|||
rgAlbumPeak = tag->rgAlbumPeak();
|
||||
rgTrackGain = tag->rgTrackGain();
|
||||
rgTrackPeak = tag->rgTrackPeak();
|
||||
[dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"];
|
||||
[dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"];
|
||||
[dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"];
|
||||
[dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"];
|
||||
[dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"];
|
||||
[dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"];
|
||||
[dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"];
|
||||
[dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"];
|
||||
|
||||
soundcheck = tag->soundcheck();
|
||||
if(!soundcheck.isEmpty()) {
|
||||
|
@ -128,6 +128,9 @@
|
|||
if(!cuesheet.isEmpty())
|
||||
[dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"];
|
||||
|
||||
if(!comment.isEmpty())
|
||||
[dict setObject:[NSString stringWithUTF8String:comment.toCString(true)] forKey:@"comment"];
|
||||
|
||||
// Try to load the image.
|
||||
NSData *image = nil;
|
||||
|
||||
|
|
|
@ -101,10 +101,10 @@
|
|||
rgAlbumPeak = tag->rgAlbumPeak();
|
||||
rgTrackGain = tag->rgTrackGain();
|
||||
rgTrackPeak = tag->rgTrackPeak();
|
||||
[dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"];
|
||||
[dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"];
|
||||
[dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"];
|
||||
[dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"];
|
||||
[dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"];
|
||||
[dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"];
|
||||
[dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"];
|
||||
[dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"];
|
||||
|
||||
soundcheck = tag->soundcheck();
|
||||
if(!soundcheck.isEmpty()) {
|
||||
|
@ -141,6 +141,9 @@
|
|||
|
||||
if(!cuesheet.isEmpty())
|
||||
[dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"];
|
||||
|
||||
if(!comment.isEmpty())
|
||||
[dict setObject:[NSString stringWithUTF8String:comment.toCString(true)] forKey:@"comment"];
|
||||
}
|
||||
|
||||
// Try to load the image.
|
||||
|
|
|
@ -32,23 +32,8 @@
|
|||
float frequency;
|
||||
long totalFrames;
|
||||
|
||||
NSString *artist;
|
||||
NSString *albumartist;
|
||||
NSString *album;
|
||||
NSString *title;
|
||||
NSString *genre;
|
||||
NSNumber *year;
|
||||
NSNumber *track;
|
||||
NSNumber *disc;
|
||||
float replayGainAlbumGain;
|
||||
float replayGainAlbumPeak;
|
||||
float replayGainTrackGain;
|
||||
float replayGainTrackPeak;
|
||||
|
||||
NSString *icygenre;
|
||||
NSString *icyalbum;
|
||||
NSString *icyartist;
|
||||
NSString *icytitle;
|
||||
NSDictionary *metaDict;
|
||||
NSDictionary *icyMetaDict;
|
||||
|
||||
NSData *albumArt;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#import "NSDictionary+Merge.h"
|
||||
|
||||
#import <FLAC/metadata.h>
|
||||
|
||||
@implementation VorbisDecoder
|
||||
|
||||
static const int MAXCHANNELS = 8;
|
||||
|
@ -101,111 +103,58 @@ long sourceTell(void *datasource) {
|
|||
[self willChangeValueForKey:@"properties"];
|
||||
[self didChangeValueForKey:@"properties"];
|
||||
|
||||
artist = @"";
|
||||
albumartist = @"";
|
||||
album = @"";
|
||||
title = @"";
|
||||
genre = @"";
|
||||
icygenre = @"";
|
||||
icyalbum = @"";
|
||||
icyartist = @"";
|
||||
icytitle = @"";
|
||||
year = @(0);
|
||||
track = @(0);
|
||||
disc = @(0);
|
||||
metaDict = [NSDictionary dictionary];
|
||||
icyMetaDict = [NSDictionary dictionary];
|
||||
albumArt = [NSData data];
|
||||
|
||||
[self updateMetadata];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)parseTag:(NSString *)tag fromTags:(vorbis_comment *)tags {
|
||||
NSMutableArray *tagStrings = [[NSMutableArray alloc] init];
|
||||
|
||||
int tagCount = vorbis_comment_query_count(tags, [tag UTF8String]);
|
||||
|
||||
for(int i = 0; i < tagCount; ++i) {
|
||||
const char *value = vorbis_comment_query(tags, [tag UTF8String], i);
|
||||
[tagStrings addObject:guess_encoding_of_string(value)];
|
||||
static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
|
||||
NSMutableArray *array = [dict valueForKey:tag];
|
||||
if(!array) {
|
||||
array = [[NSMutableArray alloc] init];
|
||||
[dict setObject:array forKey:tag];
|
||||
}
|
||||
|
||||
return [tagStrings componentsJoinedByString:@", "];
|
||||
[array addObject:value];
|
||||
}
|
||||
|
||||
- (void)updateMetadata {
|
||||
vorbis_comment *tags = ov_comment(&vorbisRef, -1);
|
||||
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
|
||||
NSData *_albumArt = albumArt;
|
||||
|
||||
if(tags) {
|
||||
NSString *_artist = [self parseTag:@"artist" fromTags:tags];
|
||||
NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags];
|
||||
NSString *_album = [self parseTag:@"album" fromTags:tags];
|
||||
NSString *_title = [self parseTag:@"title" fromTags:tags];
|
||||
NSString *_genre = [self parseTag:@"genre" fromTags:tags];
|
||||
for(int i = 0; i < tags->comments; ++i) {
|
||||
FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] };
|
||||
char *name, *value;
|
||||
if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) {
|
||||
NSString *tagName = guess_encoding_of_string(name);
|
||||
NSString *tagValue = guess_encoding_of_string(value);
|
||||
free(name);
|
||||
free(value);
|
||||
|
||||
NSString *_yearDate = [self parseTag:@"date" fromTags:tags];
|
||||
NSString *_yearYear = [self parseTag:@"year" fromTags:tags];
|
||||
tagName = [tagName lowercaseString];
|
||||
|
||||
NSNumber *_year = @(0);
|
||||
if([_yearDate length])
|
||||
_year = @([_yearDate intValue]);
|
||||
else if([_yearYear length])
|
||||
_year = @([_yearYear intValue]);
|
||||
|
||||
NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags];
|
||||
NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags];
|
||||
NSString *_trackTrack = [self parseTag:@"track" fromTags:tags];
|
||||
|
||||
NSNumber *_track = @(0);
|
||||
if([_trackNumber length])
|
||||
_track = @([_trackNumber intValue]);
|
||||
else if([_trackNum length])
|
||||
_track = @([_trackNum intValue]);
|
||||
else if([_trackTrack length])
|
||||
_track = @([_trackTrack intValue]);
|
||||
|
||||
NSString *_discNumber = [self parseTag:@"discnumber" fromTags:tags];
|
||||
NSString *_discNum = [self parseTag:@"discnum" fromTags:tags];
|
||||
NSString *_discDisc = [self parseTag:@"disc" fromTags:tags];
|
||||
|
||||
NSNumber *_disc = @(0);
|
||||
if([_discNumber length])
|
||||
_disc = @([_discNumber intValue]);
|
||||
else if([_discNum length])
|
||||
_disc = @([_discNum intValue]);
|
||||
else if([_discDisc length])
|
||||
_disc = @([_discDisc intValue]);
|
||||
|
||||
NSData *_albumArt = [NSData data];
|
||||
|
||||
size_t count = vorbis_comment_query_count(tags, "METADATA_BLOCK_PICTURE");
|
||||
if(count) {
|
||||
const char *pictureTag = vorbis_comment_query(tags, "METADATA_BLOCK_PICTURE", 0);
|
||||
flac_picture_t *picture = flac_picture_parse_from_base64(pictureTag);
|
||||
if(picture) {
|
||||
if(picture->binary && picture->binary_length) {
|
||||
_albumArt = [NSData dataWithBytes:picture->binary length:picture->binary_length];
|
||||
if([tagName isEqualToString:@"metadata_block_picture"]) {
|
||||
flac_picture_t *picture = flac_picture_parse_from_base64([tagValue UTF8String]);
|
||||
if(picture) {
|
||||
if(picture->binary && picture->binary_length) {
|
||||
_albumArt = [NSData dataWithBytes:picture->binary length:picture->binary_length];
|
||||
}
|
||||
flac_picture_free(picture);
|
||||
}
|
||||
} else {
|
||||
setDictionary(_metaDict, tagName, tagValue);
|
||||
}
|
||||
flac_picture_free(picture);
|
||||
}
|
||||
}
|
||||
|
||||
if(![_artist isEqual:artist] ||
|
||||
![_albumartist isEqual:albumartist] ||
|
||||
![_album isEqual:album] ||
|
||||
![_title isEqual:title] ||
|
||||
![_genre isEqual:genre] ||
|
||||
![_year isEqual:year] ||
|
||||
![_track isEqual:year] ||
|
||||
![_disc isEqual:disc] ||
|
||||
![_albumArt isEqual:albumArt]) {
|
||||
artist = _artist;
|
||||
albumartist = _albumartist;
|
||||
album = _album;
|
||||
title = _title;
|
||||
genre = _genre;
|
||||
year = _year;
|
||||
track = _track;
|
||||
disc = _disc;
|
||||
if(![_albumArt isEqualToData:albumArt] ||
|
||||
![_metaDict isEqualToDictionary:metaDict]) {
|
||||
metaDict = _metaDict;
|
||||
albumArt = _albumArt;
|
||||
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
|
@ -217,31 +166,35 @@ long sourceTell(void *datasource) {
|
|||
- (void)updateIcyMetadata {
|
||||
if([source seekable]) return;
|
||||
|
||||
NSString *_genre = icygenre;
|
||||
NSString *_album = icyalbum;
|
||||
NSString *_artist = icyartist;
|
||||
NSString *_title = icytitle;
|
||||
NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
Class sourceClass = [source class];
|
||||
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
|
||||
HTTPSource *httpSource = (HTTPSource *)source;
|
||||
if([httpSource hasMetadata]) {
|
||||
NSDictionary *metadata = [httpSource metadata];
|
||||
_genre = [metadata valueForKey:@"genre"];
|
||||
_album = [metadata valueForKey:@"album"];
|
||||
_artist = [metadata valueForKey:@"artist"];
|
||||
_title = [metadata valueForKey:@"title"];
|
||||
NSString *_genre = [metadata valueForKey:@"genre"];
|
||||
NSString *_album = [metadata valueForKey:@"album"];
|
||||
NSString *_artist = [metadata valueForKey:@"artist"];
|
||||
NSString *_title = [metadata valueForKey:@"title"];
|
||||
|
||||
if(_genre && [_genre length]) {
|
||||
setDictionary(_icyMetaDict, @"genre", _genre);
|
||||
}
|
||||
if(_album && [_album length]) {
|
||||
setDictionary(_icyMetaDict, @"album", _album);
|
||||
}
|
||||
if(_artist && [_artist length]) {
|
||||
setDictionary(_icyMetaDict, @"artist", _artist);
|
||||
}
|
||||
if(_title && [_title length]) {
|
||||
setDictionary(_icyMetaDict, @"title", _title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(![_genre isEqual:icygenre] ||
|
||||
![_album isEqual:icyalbum] ||
|
||||
![_artist isEqual:icyartist] ||
|
||||
![_title isEqual:icytitle]) {
|
||||
icygenre = _genre;
|
||||
icyalbum = _album;
|
||||
icyartist = _artist;
|
||||
icytitle = _title;
|
||||
if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
|
||||
icyMetaDict = _icyMetaDict;
|
||||
[self willChangeValueForKey:@"metadata"];
|
||||
[self didChangeValueForKey:@"metadata"];
|
||||
}
|
||||
|
@ -325,7 +278,10 @@ long sourceTell(void *datasource) {
|
|||
}
|
||||
|
||||
- (NSDictionary *)metadata {
|
||||
return [@{ @"artist": artist, @"albumartist": albumartist, @"album": album, @"title": title, @"genre": genre, @"year": year, @"track": track, @"disc": disc, @"albumArt": albumArt } dictionaryByMergingWith:@{ @"genre": icygenre, @"album": icyalbum, @"artist": icyartist, @"title": icytitle }];
|
||||
NSDictionary *dict1 = @{ @"albumArt": albumArt };
|
||||
NSDictionary *dict2 = [dict1 dictionaryByMergingWith:metaDict];
|
||||
NSDictionary *dict3 = [dict2 dictionaryByMergingWith:icyMetaDict];
|
||||
return dict3;
|
||||
}
|
||||
|
||||
+ (NSArray *)fileTypes {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
17C93D360B8FDA66008627D6 /* VorbisDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93D340B8FDA66008627D6 /* VorbisDecoder.m */; };
|
||||
8301C14B287810F300651A6E /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8301C14A287810F300651A6E /* libFLAC.8.dylib */; };
|
||||
83186313285CEBD2001422CC /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83186312285CEBD2001422CC /* NSDictionary+Merge.m */; };
|
||||
836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */; };
|
||||
836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */; };
|
||||
|
@ -24,6 +25,7 @@
|
|||
17C93D330B8FDA66008627D6 /* VorbisDecoder.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VorbisDecoder.h; sourceTree = "<group>"; };
|
||||
17C93D340B8FDA66008627D6 /* VorbisDecoder.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = VorbisDecoder.m; sourceTree = "<group>"; };
|
||||
32DBCF630370AF2F00C91783 /* VorbisPlugin_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VorbisPlugin_Prefix.pch; sourceTree = "<group>"; };
|
||||
8301C14A287810F300651A6E /* libFLAC.8.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libFLAC.8.dylib; path = ../../ThirdParty/flac/lib/libFLAC.8.dylib; sourceTree = "<group>"; };
|
||||
83186311285CEBD2001422CC /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSDictionary+Merge.h"; path = "../../Utils/NSDictionary+Merge.h"; sourceTree = "<group>"; };
|
||||
83186312285CEBD2001422CC /* NSDictionary+Merge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSDictionary+Merge.m"; path = "../../Utils/NSDictionary+Merge.m"; sourceTree = "<group>"; };
|
||||
8356BD1C27B46A2D0074E50C /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPSource.h; path = ../HTTPSource/HTTPSource.h; sourceTree = "<group>"; };
|
||||
|
@ -45,6 +47,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8301C14B287810F300651A6E /* libFLAC.8.dylib in Frameworks */,
|
||||
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */,
|
||||
836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */,
|
||||
836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */,
|
||||
|
@ -103,6 +106,7 @@
|
|||
1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8301C14A287810F300651A6E /* libFLAC.8.dylib */,
|
||||
836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */,
|
||||
836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */,
|
||||
1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */,
|
||||
|
@ -295,10 +299,14 @@
|
|||
"$(inherited)",
|
||||
../../ThirdParty/vorbis/include,
|
||||
../../ThirdParty/ogg/include,
|
||||
../../ThirdParty/flac/include,
|
||||
);
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(HOME)/Library/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = ../../ThirdParty/vorbis/lib;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
../../ThirdParty/vorbis/lib,
|
||||
../../ThirdParty/flac/lib,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.vorbis;
|
||||
PRODUCT_NAME = VorbisPlugin;
|
||||
SDKROOT = macosx;
|
||||
|
@ -324,10 +332,14 @@
|
|||
"$(inherited)",
|
||||
../../ThirdParty/vorbis/include,
|
||||
../../ThirdParty/ogg/include,
|
||||
../../ThirdParty/flac/include,
|
||||
);
|
||||
INFOPLIST_FILE = Info.plist;
|
||||
INSTALL_PATH = "$(HOME)/Library/Bundles";
|
||||
LIBRARY_SEARCH_PATHS = ../../ThirdParty/vorbis/lib;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
../../ThirdParty/vorbis/lib,
|
||||
../../ThirdParty/flac/lib,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cogx.vorbis;
|
||||
PRODUCT_NAME = VorbisPlugin;
|
||||
SDKROOT = macosx;
|
||||
|
|
|
@ -157,10 +157,10 @@ static NSString *get_description_tag(const char *description, const char *tag, c
|
|||
@"floatingPoint": @(NO),
|
||||
@"channels": @(channels),
|
||||
@"seekable": @(YES),
|
||||
@"replayGainAlbumGain": rgAlbumGain,
|
||||
@"replayGainAlbumPeak": rgAlbumPeak,
|
||||
@"replayGainTrackGain": rgTrackGain,
|
||||
@"replayGainTrackPeak": rgTrackPeak,
|
||||
@"replaygain_album_gain": rgAlbumGain,
|
||||
@"replaygain_album_peak": rgAlbumPeak,
|
||||
@"replaygain_track_gain": rgTrackGain,
|
||||
@"replaygain_track_peak": rgTrackPeak,
|
||||
@"codec": codec,
|
||||
@"endian": @"host",
|
||||
@"encoding": @"lossy/lossless" };
|
||||
|
|
|
@ -51,6 +51,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (NSArray *)coalesceArray:(NSArray *)input {
|
||||
if(input == nil) return input;
|
||||
|
||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
||||
for(NSString *string in input) {
|
||||
[array addObject:[self coalesceString:string]];
|
||||
}
|
||||
|
||||
return [NSArray arrayWithArray:array];
|
||||
}
|
||||
|
||||
- (NSDictionary *)coalesceEntryInfo:(NSDictionary *)entryInfo {
|
||||
if(entryInfo == nil) return entryInfo;
|
||||
|
||||
|
@ -63,6 +74,9 @@
|
|||
} else if([obj isKindOfClass:[NSData class]]) {
|
||||
NSData *dataObj = (NSData *)obj;
|
||||
[ret setObject:[self coalesceArt:dataObj] forKey:key];
|
||||
} else if([obj isKindOfClass:[NSArray class]]) {
|
||||
NSArray *arrayObj = (NSArray *)obj;
|
||||
[ret setObject:[self coalesceArray:arrayObj] forKey:key];
|
||||
} else {
|
||||
[ret setObject:obj forKey:key];
|
||||
}
|
||||
|
|
|
@ -14,9 +14,6 @@
|
|||
/* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */
|
||||
"16.title" = "Length:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */
|
||||
"18.title" = "Year:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */
|
||||
"20.title" = "Genre:";
|
||||
|
||||
|
@ -79,3 +76,9 @@
|
|||
|
||||
/* Class = "NSTextFieldCell"; title = "Codec:"; ObjectID = "QPg-Mb-Urn"; */
|
||||
"QPg-Mb-Urn.title" = "Codec:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Date:"; ObjectID = "17"; */
|
||||
"18.title" = "Date:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Comment:"; ObjectID = "cd3-Qt-hCm"; */
|
||||
"Ule-N3-dKW.title" = "Comment:";
|
||||
|
|
|
@ -16,9 +16,6 @@
|
|||
/* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */
|
||||
"16.title" = "Duración:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */
|
||||
"18.title" = "Año:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */
|
||||
"20.title" = "Género:";
|
||||
|
||||
|
@ -109,3 +106,8 @@
|
|||
/* Class = "NSTextFieldCell"; title = "N/A"; ObjectID = "Yby-OU-cqP"; */
|
||||
"Yby-OU-cqP.title" = "n/d";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Date:"; ObjectID = "17"; */
|
||||
"18.title" = "Fecha:";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Comment:"; ObjectID = "cd3-Qt-hCm"; */
|
||||
"Ule-N3-dKW.title" = "Comentarios:";
|
||||
|
|
Loading…
Reference in a new issue