Experimental tag support redesign

This redesign completely changes how tags are stored in memory. Now all
arbitrary tag names are supported, where possible. Some extra work will
be needed to support arbitrary tags with TagLib, such as replacing it
with a different library.

Translation pending for a couple of strings.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
main
Christopher Snowhill 2022-07-08 06:26:28 -07:00 committed by Christopher Snowhill
parent bea7d8e651
commit 6222e25adc
27 changed files with 868 additions and 627 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-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> <dependencies>
<deployment identifier="macosx"/> <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"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -15,16 +15,16 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <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"> <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"/> <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"/> <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"/> <value key="maxSize" type="size" width="400" height="600"/>
<view key="contentView" id="2"> <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"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Artist:" id="10"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Artist:" id="10">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -33,7 +33,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="11"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album:" id="12"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Album:" id="12">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -42,7 +42,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="13"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Track:" id="14"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Track:" id="14">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -51,7 +51,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="15"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Length:" id="16"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Length:" id="16">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -60,16 +60,16 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17"> <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"/> <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"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="19"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Genre:" id="20"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Genre:" id="20">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -78,7 +78,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="21"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Sample Rate:" id="22">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -87,7 +87,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Channels:" id="28"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Channels:" id="28">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -96,7 +96,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="29"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bitrate:" id="32"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bitrate:" id="32">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -105,7 +105,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="30"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bits Per Sample:" id="31">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -114,7 +114,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="23"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Title:" id="24"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Title:" id="24">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -123,7 +123,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="34"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="34">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -135,7 +135,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="35" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="36"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="36">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -147,7 +147,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="37" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="38"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="38">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -159,7 +159,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="39" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="40"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="40">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -171,7 +171,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="41" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="42"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="42">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -183,7 +183,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="43" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="44"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="44">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -191,11 +191,11 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections> <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> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="45" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="46"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="46">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -207,7 +207,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="50"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="50">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -223,7 +223,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="52"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="52">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -235,7 +235,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="53" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="54"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="54">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -247,7 +247,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="55" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="56"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="56">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -259,7 +259,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPg-Mb-Urn"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Codec:" id="cbq-TT-CZX">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -268,7 +268,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ijS-y2-eCZ" customClass="ToolTipTextField"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Yby-OU-cqP">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -280,7 +280,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bti-s6-SIU"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Encoding:" id="8e7-lp-K5l">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -289,7 +289,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L4f-rE-CN3" customClass="ToolTipTextField"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="v14-AG-B9D">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -301,7 +301,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gn9-9b-eV8"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Cuesheet:" id="Cu5-ia-Z5b">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -310,7 +310,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WOl-SC-4tu" customClass="ToolTipTextField"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="UyE-Mc-e39">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -322,7 +322,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="arA-Pj-ANg"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="ReplayGain:" id="qBi-M8-kEx">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -331,7 +331,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2xx-It-i6I" customClass="ToolTipTextField"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="rAo-AP-lVa">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -344,7 +344,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="84"> <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"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename:" id="85"> <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename:" id="85">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -353,7 +353,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="86" customClass="ToolTipTextField"> <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"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="87"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="87">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -365,7 +365,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vB6-9J-5qg"> <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"/> <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"> <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"/> <font key="font" metaFont="smallSystem"/>
@ -374,7 +374,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cj0-Tw-xpq" customClass="ToolTipTextField"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="B8w-o8-ZBw">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -386,7 +386,7 @@
</connections> </connections>
</textField> </textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FWx-Sc-Ocv"> <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"/> <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"> <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"/> <font key="font" metaFont="smallSystem"/>
@ -394,8 +394,17 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </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"> <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"/> <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"> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" title="N/A" id="Ial-XI-91y">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
@ -407,6 +416,19 @@
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/> <binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
</connections> </connections>
</textField> </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"> <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"/> <rect key="frame" x="0.0" y="20" width="300" height="146"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?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"> <entity name="AlbumArtwork" representedClassName="AlbumArtwork" syncable="YES" codeGenerationType="class">
<attribute name="artData" optional="YES" attributeType="Binary"/> <attribute name="artData" optional="YES" attributeType="Binary"/>
<attribute name="artHash" optional="YES" attributeType="String"/> <attribute name="artHash" optional="YES" attributeType="String"/>
@ -15,10 +15,7 @@
<attribute name="title" optional="YES" attributeType="String"/> <attribute name="title" optional="YES" attributeType="String"/>
</entity> </entity>
<entity name="PlaylistEntry" representedClassName="PlaylistEntry" syncable="YES" codeGenerationType="class"> <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="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="bitrate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="bitsPerSample" 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"/> <attribute name="channelConfig" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
@ -30,19 +27,18 @@
<attribute name="currentPosition" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/> <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="dbIndex" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deLeted" optional="YES" attributeType="Boolean" 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="encoding" optional="YES" attributeType="String"/>
<attribute name="endian" 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="entryId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="error" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="error" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="errorMessage" optional="YES" attributeType="String"/> <attribute name="errorMessage" optional="YES" attributeType="String"/>
<attribute name="floatingPoint" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <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="index" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="metadataCompressed" optional="YES" attributeType="Binary"/>
<attribute name="metadataDecompressedSize" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="metadataLoaded" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="metadataLoaded" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="queued" 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="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="removed" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="replayGainAlbumGain" optional="YES" attributeType="Float" defaultValueString="0.0" 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"/> <attribute name="replayGainAlbumPeak" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
@ -55,12 +51,10 @@
<attribute name="spotlightTrack" optional="YES" attributeType="String"/> <attribute name="spotlightTrack" optional="YES" attributeType="String"/>
<attribute name="stopAfter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="stopAfter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="totalFrames" optional="YES" attributeType="Integer 64" defaultValueString="0" 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="trashUrlString" optional="YES" attributeType="String"/>
<attribute name="unSigned" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="unSigned" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="urlString" optional="YES" attributeType="String"/> <attribute name="urlString" optional="YES" attributeType="String"/>
<attribute name="volume" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="YES"/> <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>
<entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class"> <entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class">
<attribute name="bookmark" optional="YES" attributeType="Binary"/> <attribute name="bookmark" optional="YES" attributeType="Binary"/>
@ -70,7 +64,7 @@
<elements> <elements>
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/> <element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
<element name="PlayCount" positionX="-18" positionY="171" width="128" height="149"/> <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="629"/>
<element name="SandboxToken" positionX="-18" positionY="171" width="128" height="74"/> <element name="SandboxToken" positionX="-18" positionY="171" width="128" height="74"/>
</elements> </elements>
</model> </model>

View File

@ -74,6 +74,25 @@
@property(nonatomic, readonly) float rating; @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;
@property(nonatomic) NSDictionary *_Nullable metadataBlob;
- (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; - (void)setMetadata:(NSDictionary *_Nonnull)metadata;
@end @end

View File

@ -16,11 +16,28 @@
#import "SHA256Digest.h" #import "SHA256Digest.h"
#import "SecondsFormatter.h" #import "SecondsFormatter.h"
#import <compression.h>
extern NSPersistentContainer *kPersistentContainer; extern NSPersistentContainer *kPersistentContainer;
extern NSMutableDictionary<NSString *, AlbumArtwork *> *kArtworkDictionary; extern NSMutableDictionary<NSString *, AlbumArtwork *> *kArtworkDictionary;
static NSMutableDictionary *kMetadataBlobCache = nil;
static NSMutableDictionary *kMetadataCache = nil;
static void *kCompressionScratchBuffer = NULL;
static void *kDecompressionScratchBuffer = NULL;
@implementation PlaylistEntry (Extension) @implementation PlaylistEntry (Extension)
+ (void)initialize {
if(!kMetadataBlobCache) {
kMetadataBlobCache = [[NSMutableDictionary alloc] init];
}
if(!kMetadataCache) {
kMetadataCache = [[NSMutableDictionary alloc] init];
}
}
// The following read-only keys depend on the values of other properties // The following read-only keys depend on the values of other properties
+ (NSSet *)keyPathsForValuesAffectingUrl { + (NSSet *)keyPathsForValuesAffectingUrl {
@ -503,8 +520,72 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path) {
self.error = YES; self.error = YES;
self.errorMessage = NSLocalizedStringFromTableInBundle(@"ErrorMetadata", nil, [NSBundle bundleForClass:[self class]], @""); self.errorMessage = NSLocalizedStringFromTableInBundle(@"ErrorMetadata", nil, [NSBundle bundleForClass:[self class]], @"");
} else { } else {
NSMutableDictionary *metaDict = [[NSMutableDictionary alloc] init];
self.volume = 1; 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 = metaDict;
} }
[self setMetadataLoaded:YES]; [self setMetadataLoaded:YES];
@ -574,4 +655,318 @@ 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 {
NSMutableDictionary *dict = [kMetadataCache objectForKey:self.urlString];
if(dict) {
NSString *value = [dict objectForKey:tagName];
if(value) {
return value;
}
}
id metaObj = self.metadataBlob;
if(metaObj && [metaObj isKindOfClass:[NSDictionary class]]) {
NSDictionary *metaDict = (NSDictionary *)metaObj;
NSArray *values = [metaDict objectForKey:tagName];
if(values) {
NSString *value = [values componentsJoinedByString:@", "];
if(!dict) {
dict = [[NSMutableDictionary alloc] init];
[kMetadataCache setObject:dict forKey:self.urlString];
}
[dict setObject:value forKey:tagName];
return value;
}
}
return nil;
}
- (void)deleteAllValues {
self.metadataBlob = nil;
[kMetadataCache removeObjectForKey:self.urlString];
}
- (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 {
NSMutableDictionary *dict = [kMetadataCache objectForKey:self.urlString];
if(dict) {
[dict removeObjectForKey:tagName];
}
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];
}
}
- (NSDictionary *)metadataBlob {
{
NSDictionary *blob = [kMetadataBlobCache objectForKey:self.urlString];
if(blob) {
return blob;
}
}
if(self.metadataCompressed == nil || self.metadataDecompressedSize == 0) {
return @{};
}
if(!kDecompressionScratchBuffer) {
size_t scratchSize = compression_decode_scratch_buffer_size(COMPRESSION_ZLIB);
kDecompressionScratchBuffer = malloc(scratchSize);
}
void *decompressionBuffer = malloc(self.metadataDecompressedSize);
size_t decodedBytes = compression_decode_buffer(decompressionBuffer, self.metadataDecompressedSize, [self.metadataCompressed bytes], [self.metadataCompressed length], kDecompressionScratchBuffer, COMPRESSION_ZLIB);
NSData *decodedData = [NSData dataWithBytes:decompressionBuffer length:decodedBytes];
free(decompressionBuffer);
NSSet *allowed = [NSSet setWithArray:@[[NSDictionary class], [NSMutableDictionary class], [NSArray class], [NSMutableArray class], [NSString class]]];
NSError *error = nil;
NSDictionary *dict;
dict = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowed
fromData:decodedData
error:&error];
if(!dict) {
dict = @{};
}
[kMetadataBlobCache setObject:dict forKey:self.urlString];
return dict;
}
- (void)setMetadataBlob:(NSMutableDictionary *)metadataBlob {
[kMetadataCache removeObjectForKey:self.urlString];
if(metadataBlob == nil) {
self.metadataCompressed = nil;
self.metadataDecompressedSize = 0;
[kMetadataBlobCache removeObjectForKey:self.urlString];
return;
}
[kMetadataBlobCache setObject:metadataBlob forKey:self.urlString];
NSError *error = nil;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:metadataBlob
requiringSecureCoding:YES
error:&error];
if(!kCompressionScratchBuffer) {
size_t scratchBufferSize = compression_encode_scratch_buffer_size(COMPRESSION_ZLIB);
kCompressionScratchBuffer = malloc(scratchBufferSize);
}
size_t fullSize = [data length];
size_t compressedSize = fullSize * 3 / 2 + 256;
void *compressedBuffer = malloc(compressedSize);
size_t compressedOutSize = compression_encode_buffer(compressedBuffer, compressedSize, [data bytes], fullSize, kCompressionScratchBuffer, COMPRESSION_ZLIB);
NSData *compressData = [NSData dataWithBytes:compressedBuffer length:compressedOutSize];
free(compressedBuffer);
self.metadataCompressed = compressData;
self.metadataDecompressedSize = fullSize;
}
@end @end

View File

@ -861,6 +861,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
} }
- (BOOL)addDataStore { - (BOOL)addDataStore {
BOOL dataMigrated = [[NSUserDefaults standardUserDefaults] boolForKey:@"metadataMigrated"];
NSPersistentContainer *pc = playlistController.persistentContainer; NSPersistentContainer *pc = playlistController.persistentContainer;
if(pc) { if(pc) {
NSManagedObjectContext *moc = pc.viewContext; NSManagedObjectContext *moc = pc.viewContext;
@ -902,10 +904,17 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
++index; ++index;
} }
[resultsCopy removeObjectsAtIndexes:pruneSet]; [resultsCopy removeObjectsAtIndexes:pruneSet];
if([pruneSet count]) {
if(!dataMigrated) {
for(PlaylistEntry *pe in resultsCopy) {
pe.metadataLoaded = NO;
}
}
if([pruneSet count] || !dataMigrated) {
[playlistController commitPersistentStore]; [playlistController commitPersistentStore];
} }
results = [NSArray arrayWithArray:resultsCopy]; results = [NSArray arrayWithArray:resultsCopy];
{ {
@ -917,6 +926,8 @@ NSURL *_Nullable urlForPath(NSString *_Nullable path);
[playlistController readQueueFromDataStore]; [playlistController readQueueFromDataStore];
[playlistController readShuffleListFromDataStore]; [playlistController readShuffleListFromDataStore];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"metadataMigrated"];
return YES; return YES;
} }
return NO; return NO;

View File

@ -85,10 +85,10 @@
if([[track track] intValue]) [cuesheetMetadata setValue:@([[track track] intValue]) forKey:@"track"]; if([[track track] intValue]) [cuesheetMetadata setValue:@([[track track] intValue]) forKey:@"track"];
if([track genre]) [cuesheetMetadata setValue:[track genre] forKey:@"genre"]; if([track genre]) [cuesheetMetadata setValue:[track genre] forKey:@"genre"];
if([[track year] intValue]) [cuesheetMetadata setValue:@([[track year] intValue]) forKey:@"year"]; if([[track year] intValue]) [cuesheetMetadata setValue:@([[track year] intValue]) forKey:@"year"];
if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replayGainAlbumGain"]; if([track albumGain]) [cuesheetMetadata setValue:@([track albumGain]) forKey:@"replaygain_album_gain"];
if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replayGainAlbumPeak"]; if([track albumPeak]) [cuesheetMetadata setValue:@([track albumPeak]) forKey:@"replaygain_album_peak"];
if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replayGainTrackGain"]; if([track trackGain]) [cuesheetMetadata setValue:@([track trackGain]) forKey:@"replaygain_track_gain"];
if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replayGainTrackPeak"]; if([track trackPeak]) [cuesheetMetadata setValue:@([track trackPeak]) forKey:@"replaygain_track_peak"];
return [NSDictionary dictionaryWithDictionary:cuesheetMetadata]; return [NSDictionary dictionaryWithDictionary:cuesheetMetadata];
} }

View File

@ -60,23 +60,13 @@ int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence);
BOOL endOfAudio; BOOL endOfAudio;
int metadataIndex; 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; int attachedPicIndex;
NSData *albumArt; NSData *albumArt;
NSDictionary *metaDict;
NSDictionary *id3Metadata; NSDictionary *id3Metadata;
} }

View File

@ -465,21 +465,9 @@ static uint8_t reverse_bits[0x100];
seekedToStart = !seekable; seekedToStart = !seekable;
artist = @""; id3Metadata = [[NSDictionary alloc] init];
albumartist = @""; metaDict = [NSDictionary dictionary];
album = @"";
title = @"";
genre = @"";
year = @(0);
track = @(0);
disc = @(0);
replayGainAlbumGain = 0.0;
replayGainAlbumPeak = 0.0;
replayGainTrackGain = 0.0;
replayGainTrackPeak = 0.0;
volumeScale = 1.0;
albumArt = [NSData data]; albumArt = [NSData data];
id3Metadata = @{};
metadataUpdated = NO; metadataUpdated = NO;
[self updateMetadata]; [self updateMetadata];
@ -534,21 +522,18 @@ static uint8_t reverse_bits[0x100];
[self close]; [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 { - (void)updateMetadata {
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
const AVDictionaryEntry *tag = NULL; 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) { for(size_t i = 0; i < 2; ++i) {
AVDictionary *metadata; AVDictionary *metadata;
if(i == 0) { if(i == 0) {
@ -567,62 +552,45 @@ static uint8_t reverse_bits[0x100];
if(!strcasecmp(tag->key, "streamtitle")) { if(!strcasecmp(tag->key, "streamtitle")) {
NSString *artistTitle = guess_encoding_of_string(tag->value); NSString *artistTitle = guess_encoding_of_string(tag->value);
NSArray *splitValues = [artistTitle componentsSeparatedByString:@" - "]; NSArray *splitValues = [artistTitle componentsSeparatedByString:@" - "];
_artist = @""; NSString *_artist = @"";
_title = [splitValues objectAtIndex:0]; NSString *_title = [splitValues objectAtIndex:0];
if([splitValues count] > 1) { if([splitValues count] > 1) {
_artist = _title; _artist = _title;
_title = [splitValues objectAtIndex:1]; _title = [splitValues objectAtIndex:1];
setDictionary(_metaDict, @"artist", _artist);
setDictionary(_metaDict, @"title", _title);
} else {
setDictionary(_metaDict, @"title", _title);
} }
} else if(!strcasecmp(tag->key, "icy-url")) { } else if(!strcasecmp(tag->key, "icy-url")) {
_album = guess_encoding_of_string(tag->value); setDictionary(_metaDict, @"album", guess_encoding_of_string(tag->value));
} else if(!strcasecmp(tag->key, "icy-genre") || } else if(!strcasecmp(tag->key, "icy-genre")) {
!strcasecmp(tag->key, "genre")) { setDictionary(_metaDict, @"genre", guess_encoding_of_string(tag->value));
_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);
} else if(!strcasecmp(tag->key, "title")) { } else if(!strcasecmp(tag->key, "title")) {
NSString *_tag = guess_encoding_of_string(tag->value); NSString *_tag = guess_encoding_of_string(tag->value);
if(i == 0 && formatCtx->nb_chapters > 1) { if(i == 0 && formatCtx->nb_chapters > 1) {
_album = _tag; setDictionary(_metaDict, @"album", _tag);
} else { } else {
_title = _tag; setDictionary(_metaDict, @"title", _tag);
} }
} else if(!strcasecmp(tag->key, "date") || } else if(!strcasecmp(tag->key, "date_recorded")) {
!strcasecmp(tag->key, "date_recorded")) { setDictionary(_metaDict, @"date", guess_encoding_of_string(tag->value));
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, "replaygain_gain")) { } else if(!strcasecmp(tag->key, "replaygain_gain")) {
// global or chapter gain // global or chapter gain
NSString *rgValue = guess_encoding_of_string(tag->value); NSString *tagName;
if(i == 0) _replayGainAlbumGain = [rgValue floatValue]; if(i == 0)
else _replayGainTrackGain = [rgValue floatValue]; 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")) { } else if(!strcasecmp(tag->key, "replaygain_peak")) {
// global or chapter peak // global or chapter peak
NSString *rgValue = guess_encoding_of_string(tag->value); NSString *tagName;
if(i == 0) _replayGainAlbumPeak = [rgValue floatValue]; if(i == 0)
else _replayGainTrackPeak = [rgValue floatValue]; tagName = @"replaygain_album_peak";
else
tagName = @"replaygain_track_peak";
setDictionary(_metaDict, tagName, guess_encoding_of_string(tag->value));
} else if(!strcasecmp(tag->key, "iTunNORM")) { } else if(!strcasecmp(tag->key, "iTunNORM")) {
NSString *tagString = guess_encoding_of_string(tag->value); NSString *tagString = guess_encoding_of_string(tag->value);
NSArray *tag = [tagString componentsSeparatedByString:@" "]; NSArray *tag = [tagString componentsSeparatedByString:@" "];
@ -642,8 +610,11 @@ static uint8_t reverse_bits[0x100];
float volume1 = -log10((double)(hexvalue1) / 1000) * 10; float volume1 = -log10((double)(hexvalue1) / 1000) * 10;
float volume2 = -log10((double)(hexvalue2) / 1000) * 10; float volume2 = -log10((double)(hexvalue2) / 1000) * 10;
float volumeToUse = MIN(volume1, volume2); 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; HTTPSource *httpSource = (HTTPSource *)source;
if([httpSource hasMetadata]) { if([httpSource hasMetadata]) {
NSDictionary *metadata = [httpSource metadata]; NSDictionary *metadata = [httpSource metadata];
_genre = [metadata valueForKey:@"genre"]; NSString *_genre = [metadata valueForKey:@"genre"];
_album = [metadata valueForKey:@"album"]; NSString *_album = [metadata valueForKey:@"album"];
_artist = [metadata valueForKey:@"artist"]; NSString *_artist = [metadata valueForKey:@"artist"];
_title = [metadata valueForKey:@"title"]; 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] || if(![_metaDict isEqualToDictionary:metaDict]) {
![_albumartist isEqual:albumartist] || metaDict = _metaDict;
![_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(![source seekable]) { if(![source seekable]) {
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
@ -700,7 +660,7 @@ static uint8_t reverse_bits[0x100];
Class tagReader = NSClassFromString(@"TagLibID3v2Reader"); Class tagReader = NSClassFromString(@"TagLibID3v2Reader");
if(tagReader && [tagReader respondsToSelector:@selector(metadataForTag:)]) { if(tagReader && [tagReader respondsToSelector:@selector(metadataForTag:)]) {
NSDictionary *_id3Metadata = [tagReader metadataForTag:tag]; NSDictionary *_id3Metadata = [tagReader metadataForTag:tag];
if(![_id3Metadata isEqualTo:id3Metadata]) { if(![_id3Metadata isEqualToDictionary:id3Metadata]) {
id3Metadata = _id3Metadata; id3Metadata = _id3Metadata;
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
@ -710,7 +670,7 @@ static uint8_t reverse_bits[0x100];
- (void)updateArtwork { - (void)updateArtwork {
NSData *_albumArt = [NSData dataWithBytes:lastReadPacket->data length:lastReadPacket->size]; NSData *_albumArt = [NSData dataWithBytes:lastReadPacket->data length:lastReadPacket->size];
if(![_albumArt isEqual:albumArt]) { if(![_albumArt isEqualToData:albumArt]) {
albumArt = _albumArt; albumArt = _albumArt;
if(![source seekable]) { if(![source seekable]) {
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
@ -1010,7 +970,10 @@ static uint8_t reverse_bits[0x100];
} }
- (NSDictionary *)metadata { - (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 { + (NSArray *)fileTypes {

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93F040B8FF67A008627D6 /* FlacDecoder.m */; }; 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 */; }; 836EF0DA27BB970B00BF35B2 /* libFLAC.8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0D927BB970B00BF35B2 /* libFLAC.8.dylib */; };
83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AA660A27B7DAE40098D4B8 /* cuesheet.m */; }; 83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AA660A27B7DAE40098D4B8 /* cuesheet.m */; };
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 83747C4A2862DCF40021245F /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
@ -89,6 +92,8 @@
08FB77AFFE84173DC02AAC07 /* Classes */ = { 08FB77AFFE84173DC02AAC07 /* Classes */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8301C145287805F500651A6E /* NSDictionary+Merge.h */,
8301C146287805F500651A6E /* NSDictionary+Merge.m */,
83AA660A27B7DAE40098D4B8 /* cuesheet.m */, 83AA660A27B7DAE40098D4B8 /* cuesheet.m */,
8356BD1927B3CCBB0074E50C /* HTTPSource.h */, 8356BD1927B3CCBB0074E50C /* HTTPSource.h */,
8384912D180816C900E7332D /* Logging.h */, 8384912D180816C900E7332D /* Logging.h */,
@ -211,6 +216,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8301C147287805F500651A6E /* NSDictionary+Merge.m in Sources */,
17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */, 17C93F080B8FF67A008627D6 /* FlacDecoder.m in Sources */,
83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */, 83AA660B27B7DAE40098D4B8 /* cuesheet.m in Sources */,
); );

View File

@ -36,18 +36,8 @@
BOOL streamOpened; BOOL streamOpened;
BOOL abortFlag; BOOL abortFlag;
NSString *artist; NSDictionary *metaDict;
NSString *albumartist; NSDictionary *icyMetaDict;
NSString *album;
NSString *title;
NSString *genre;
NSNumber *year;
NSNumber *track;
NSNumber *disc;
float replayGainAlbumGain;
float replayGainAlbumPeak;
float replayGainTrackGain;
float replayGainTrackPeak;
NSData *albumArt; NSData *albumArt;

View File

@ -12,6 +12,8 @@
#import "HTTPSource.h" #import "HTTPSource.h"
#import "NSDictionary+Merge.h"
extern void grabbag__cuesheet_emit(NSString **out, const FLAC__StreamMetadata *cuesheet, const char *file_reference); extern void grabbag__cuesheet_emit(NSString **out, const FLAC__StreamMetadata *cuesheet, const char *file_reference);
@implementation FlacDecoder @implementation FlacDecoder
@ -178,6 +180,15 @@ FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder,
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; 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 // This callback is only called for STREAMINFO blocks
void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { void MetadataCallback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) {
// Some flacs observed in the wild have multiple STREAMINFO metadata blocks, // 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) { if(metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
NSString *_artist = flacDecoder->artist; NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
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;
NSString *_cuesheet = flacDecoder->cuesheet; NSString *_cuesheet = flacDecoder->cuesheet;
const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment; const FLAC__StreamMetadata_VorbisComment *vorbis_comment = &metadata->data.vorbis_comment;
for(int i = 0; i < vorbis_comment->num_comments; ++i) { 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(_name);
free(_value); free(_value);
name = [name lowercaseString]; name = [name lowercaseString];
if([name isEqualToString:@"artist"]) { if([name isEqualToString:@"cuesheet"]) {
_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"]) {
_cuesheet = value; _cuesheet = value;
flacDecoder->cuesheetFound = YES; 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"]) { } else if([name isEqualToString:@"waveformatextensible_channel_mask"]) {
if([value hasPrefix:@"0x"]) { if([value hasPrefix:@"0x"]) {
char *end; char *end;
const char *_value = [value UTF8String] + 2; const char *_value = [value UTF8String] + 2;
flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16); flacDecoder->channelConfig = (uint32_t)strtoul(_value, &end, 16);
} }
} else {
setDictionary(_metaDict, name, value);
} }
} }
} }
if(![_artist isEqual:flacDecoder->artist] || if(![_metaDict isEqualToDictionary:flacDecoder->metaDict]) {
![_albumartist isEqual:flacDecoder->albumartist] || flacDecoder->metaDict = _metaDict;
![_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(![flacDecoder->source seekable]) { if(![flacDecoder->source seekable]) {
[flacDecoder willChangeValueForKey:@"metadata"]; [flacDecoder willChangeValueForKey:@"metadata"];
@ -348,18 +297,8 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
isOggFlac = YES; isOggFlac = YES;
} }
artist = @""; metaDict = [NSDictionary dictionary];
albumartist = @""; icyMetaDict = [NSDictionary dictionary];
album = @"";
title = @"";
genre = @"";
year = @(0);
track = @(0);
disc = @(0);
replayGainAlbumGain = 0.0;
replayGainAlbumPeak = 0.0;
replayGainTrackGain = 0.0;
replayGainTrackPeak = 0.0;
albumArt = [NSData data]; albumArt = [NSData data];
cuesheetFound = NO; cuesheetFound = NO;
cuesheet = @""; cuesheet = @"";
@ -461,20 +400,28 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
Class sourceClass = [source class]; Class sourceClass = [source class];
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
HTTPSource *httpSource = (HTTPSource *)source; HTTPSource *httpSource = (HTTPSource *)source;
NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
if([httpSource hasMetadata]) { if([httpSource hasMetadata]) {
NSDictionary *metadata = [httpSource metadata]; NSDictionary *metadata = [httpSource metadata];
NSString *_genre = [metadata valueForKey:@"genre"]; NSString *_genre = [metadata valueForKey:@"genre"];
NSString *_album = [metadata valueForKey:@"album"]; NSString *_album = [metadata valueForKey:@"album"];
NSString *_artist = [metadata valueForKey:@"artist"]; NSString *_artist = [metadata valueForKey:@"artist"];
NSString *_title = [metadata valueForKey:@"title"]; NSString *_title = [metadata valueForKey:@"title"];
if(![_genre isEqualToString:genre] ||
![_album isEqualToString:album] || if(_genre && [_genre length]) {
![_artist isEqualToString:artist] || setDictionary(_icyMetaDict, @"genre", _genre);
![_title isEqualToString:title]) { }
genre = _genre; if(_album && [_album length]) {
album = _album; setDictionary(_icyMetaDict, @"album", _album);
artist = _artist; }
title = _title; 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 willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
} }
@ -557,7 +504,10 @@ void ErrorCallback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorS
} }
- (NSDictionary *)metadata { - (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 { + (NSArray *)fileTypes {

View File

@ -24,12 +24,6 @@
int tagLengthMs; int tagLengthMs;
int tagFadeMs; int tagFadeMs;
float replayGainAlbumGain;
float replayGainAlbumPeak;
float replayGainTrackGain;
float replayGainTrackPeak;
float volume;
int type; int type;
int sampleRate; int sampleRate;

View File

@ -220,6 +220,20 @@ static int parse_time_crap(NSString *value) {
return totalSeconds; 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) { 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; 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"]) { if([taglc isEqualToString:@"game"]) {
taglc = @"album"; 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 isEqualToString:@"length"]) {
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"]) {
state->tag_length_ms = parse_time_crap(svalue); state->tag_length_ms = parse_time_crap(svalue);
setDictionary(state->info, @"psf_length", svalue);
} else if([taglc isEqualToString:@"fade"]) { } else if([taglc isEqualToString:@"fade"]) {
state->tag_fade_ms = parse_time_crap(svalue); state->tag_fade_ms = parse_time_crap(svalue);
setDictionary(state->info, @"psf_fade", svalue);
} else if([taglc isEqualToString:@"utf8"]) { } else if([taglc isEqualToString:@"utf8"]) {
state->utf8 = true; state->utf8 = true;
} else if([taglc isEqualToString:@"title"] || } else {
[taglc isEqualToString:@"albumartist"] || setDictionary(state->info, taglc, svalue);
[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];
} }
return 0; return 0;
@ -1218,12 +1204,6 @@ static int usf_info(void *context, const char *name, const char *value) {
tagFadeMs = (int)ceil(defaultFade * 1000.0); tagFadeMs = (int)ceil(defaultFade * 1000.0);
} }
replayGainAlbumGain = info.albumGain;
replayGainAlbumPeak = info.albumPeak;
replayGainTrackGain = info.trackGain;
replayGainTrackPeak = info.trackPeak;
volume = info.volume;
metadataList = info.info; metadataList = info.info;
framesLength = [self retrieveFrameCount:tagLengthMs]; framesLength = [self retrieveFrameCount:tagLengthMs];
@ -1573,11 +1553,6 @@ static int usf_info(void *context, const char *name, const char *value) {
@"totalFrames": @(totalFrames), @"totalFrames": @(totalFrames),
@"bitrate": @(0), @"bitrate": @(0),
@"seekable": @(YES), @"seekable": @(YES),
@"replayGainAlbumGain": @(replayGainAlbumGain),
@"replayGainAlbumPeak": @(replayGainAlbumPeak),
@"replayGainTrackGain": @(replayGainTrackGain),
@"replayGainTrackPeak": @(replayGainTrackPeak),
@"volume": @(volume),
@"codec": codec, @"codec": codec,
@"endian": @"host", @"endian": @"host",
@"encoding": @"synthesized" }; @"encoding": @"synthesized" };

View File

@ -26,6 +26,15 @@
return 1.0f; 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 { + (NSDictionary *)metadataForURL:(NSURL *)url {
id audioSourceClass = NSClassFromString(@"AudioSource"); id audioSourceClass = NSClassFromString(@"AudioSource");
id<CogSource> source = [audioSourceClass audioSourceForURL:url]; id<CogSource> source = [audioSourceClass audioSourceForURL:url];
@ -62,9 +71,7 @@
midi_meta_data_item item; midi_meta_data_item item;
bool remap_display_name = !metadata.get_item("title", item); bool remap_display_name = !metadata.get_item("title", item);
NSArray *allowedKeys = @[@"title", @"artist", @"albumartist", @"album", @"genre"]; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:10];
for(size_t i = 0; i < metadata.get_count(); ++i) { for(size_t i = 0; i < metadata.get_count(); ++i) {
const midi_meta_data_item &item = metadata[i]; const midi_meta_data_item &item = metadata[i];
@ -72,11 +79,7 @@
if(![name isEqualToString:@"type"]) { if(![name isEqualToString:@"type"]) {
if(remap_display_name && [name isEqualToString:@"display_name"]) if(remap_display_name && [name isEqualToString:@"display_name"])
name = @"title"; name = @"title";
if([allowedKeys containsObject:name]) { setDictionary(dict, name, guess_encoding_of_string(item.m_value.c_str()));
[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];
}
} }
} }

View File

@ -27,6 +27,15 @@
return 1.0f; 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 { + (NSDictionary *)metadataForURL:(NSURL *)url {
id audioSourceClass = NSClassFromString(@"AudioSource"); id audioSourceClass = NSClassFromString(@"AudioSource");
id<CogSource> source = [audioSourceClass audioSourceForURL:url]; id<CogSource> source = [audioSourceClass audioSourceForURL:url];
@ -55,41 +64,25 @@
std::map<std::string, std::string> ctls; std::map<std::string, std::string> ctls;
openmpt::module *mod = new openmpt::module(data, std::clog, ctls); openmpt::module *mod = new openmpt::module(data, std::clog, ctls);
NSString *title = nil; NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSString *artist = nil;
// NSString * comment = nil;
NSString *date = nil;
NSString *type = nil;
std::vector<std::string> keys = mod->get_metadata_keys(); std::vector<std::string> keys = mod->get_metadata_keys();
for(std::vector<std::string>::iterator key = keys.begin(); key != keys.end(); ++key) { for(std::vector<std::string>::iterator key = keys.begin(); key != keys.end(); ++key) {
if(*key == "title") NSString *tag = guess_encoding_of_string((*key).c_str());
title = guess_encoding_of_string(mod->get_metadata(*key).c_str()); NSString *value = guess_encoding_of_string(mod->get_metadata(*key).c_str());
else if(*key == "artist") if(*key == "type")
artist = guess_encoding_of_string(mod->get_metadata(*key).c_str()); continue;
/*else if ( *key == "message" ) else if(*key == "type_long") {
comment = guess_encoding_of_string(mod->get_metadata( *key ).c_str());*/ setDictionary(dict, @"codec", value);
else if(*key == "date") } else {
date = guess_encoding_of_string(mod->get_metadata(*key).c_str()); setDictionary(dict, tag, value);
else if(*key == "type_long") }
type = guess_encoding_of_string(mod->get_metadata(*key).c_str());
} }
delete mod; delete mod;
if(title == nil) return dict;
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 };
} catch(std::exception & /*e*/) { } catch(std::exception & /*e*/) {
return 0; return 0;
} }

View File

@ -23,23 +23,13 @@
int channels; int channels;
long totalFrames; long totalFrames;
NSString *artist; NSDictionary *metaDict;
NSString *albumartist; NSDictionary *icyMetaDict;
NSString *album;
NSString *title;
NSString *genre;
NSNumber *year;
NSNumber *track;
NSNumber *disc;
float replayGainAlbumGain;
float replayGainTrackGain;
NSString *icygenre;
NSString *icyalbum;
NSString *icyartist;
NSString *icytitle;
NSData *albumArt; NSData *albumArt;
float replayGainAlbumGain;
float replayGainTrackGain;
} }
@end @end

View File

@ -16,6 +16,8 @@
#import "NSDictionary+Merge.h" #import "NSDictionary+Merge.h"
#import <FLAC/metadata.h>
@implementation OpusFile @implementation OpusFile
static const int MAXCHANNELS = 8; static const int MAXCHANNELS = 8;
@ -121,113 +123,60 @@ opus_int64 sourceTell(void *_stream) {
[self willChangeValueForKey:@"properties"]; [self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"];
artist = @""; metaDict = [NSDictionary dictionary];
albumartist = @""; icyMetaDict = [NSDictionary dictionary];
album = @"";
title = @"";
genre = @"";
icygenre = @"";
icyalbum = @"";
icyartist = @"";
icytitle = @"";
year = @(0);
track = @(0);
disc = @(0);
albumArt = [NSData data]; albumArt = [NSData data];
[self updateMetadata]; [self updateMetadata];
return YES; return YES;
} }
- (NSString *)parseTag:(NSString *)tag fromTags:(const OpusTags *)tags { static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
NSMutableArray *tagStrings = [[NSMutableArray alloc] init]; NSMutableArray *array = [dict valueForKey:tag];
if(!array) {
int tagCount = opus_tags_query_count(tags, [tag UTF8String]); array = [[NSMutableArray alloc] init];
[dict setObject:array forKey:tag];
for(int i = 0; i < tagCount; ++i) {
const char *value = opus_tags_query(tags, [tag UTF8String], i);
[tagStrings addObject:guess_encoding_of_string(value)];
} }
[array addObject:value];
return [tagStrings componentsJoinedByString:@", "];
} }
- (void)updateMetadata { - (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) { if(tags) {
NSString *_artist = [self parseTag:@"artist" fromTags:tags]; for(int i = 0; i < tags->comments; ++i) {
NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags]; FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] };
NSString *_album = [self parseTag:@"album" fromTags:tags]; char *name, *value;
NSString *_title = [self parseTag:@"title" fromTags:tags]; if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) {
NSString *_genre = [self parseTag:@"genre" fromTags:tags]; 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]; tagName = [tagName lowercaseString];
NSString *_yearYear = [self parseTag:@"year" fromTags:tags];
NSNumber *_year = @(0); if([tagName isEqualToString:@"metadata_block_picture"]) {
if([_yearDate length]) OpusPictureTag _pic = { 0 };
_year = @([_yearDate intValue]); if(opus_picture_tag_parse(&_pic, [tagValue UTF8String]) >= 0) {
else if([_yearYear length]) if(_pic.format == OP_PIC_FORMAT_PNG ||
_year = @([_yearYear intValue]); _pic.format == OP_PIC_FORMAT_JPEG ||
_pic.format == OP_PIC_FORMAT_GIF) {
NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags]; _albumArt = [NSData dataWithBytes:_pic.data length:_pic.data_length];
NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags]; }
NSString *_trackTrack = [self parseTag:@"track" fromTags:tags]; opus_picture_tag_clear(&_pic);
}
NSNumber *_track = @(0); } else {
if([_trackNumber length]) setDictionary(_metaDict, tagName, tagValue);
_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];
} }
opus_picture_tag_clear(&_pic);
} }
} }
if(![_artist isEqual:artist] || if(![_albumArt isEqualToData:albumArt] ||
![_albumartist isEqual:albumartist] || ![_metaDict isEqualToDictionary:metaDict]) {
![_album isEqual:album] || metaDict = _metaDict;
![_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;
albumArt = _albumArt; albumArt = _albumArt;
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
@ -239,31 +188,35 @@ opus_int64 sourceTell(void *_stream) {
- (void)updateIcyMetadata { - (void)updateIcyMetadata {
if([source seekable]) return; if([source seekable]) return;
NSString *_genre = icygenre; NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
NSString *_album = icyalbum;
NSString *_artist = icyartist;
NSString *_title = icytitle;
Class sourceClass = [source class]; Class sourceClass = [source class];
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
HTTPSource *httpSource = (HTTPSource *)source; HTTPSource *httpSource = (HTTPSource *)source;
if([httpSource hasMetadata]) { if([httpSource hasMetadata]) {
NSDictionary *metadata = [httpSource metadata]; NSDictionary *metadata = [httpSource metadata];
_genre = [metadata valueForKey:@"genre"]; NSString *_genre = [metadata valueForKey:@"genre"];
_album = [metadata valueForKey:@"album"]; NSString *_album = [metadata valueForKey:@"album"];
_artist = [metadata valueForKey:@"artist"]; NSString *_artist = [metadata valueForKey:@"artist"];
_title = [metadata valueForKey:@"title"]; 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] || if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
![_album isEqual:icyalbum] || icyMetaDict = _icyMetaDict;
![_artist isEqual:icyartist] ||
![_title isEqual:icytitle]) {
icygenre = _genre;
icyalbum = _album;
icyartist = _artist;
icytitle = _title;
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
} }
@ -338,15 +291,18 @@ opus_int64 sourceTell(void *_stream) {
@"totalFrames": @(totalFrames), @"totalFrames": @(totalFrames),
@"bitrate": @(bitrate), @"bitrate": @(bitrate),
@"seekable": @([source seekable] && seekable), @"seekable": @([source seekable] && seekable),
@"replayGainAlbumGain": @(replayGainAlbumGain), @"replaygain_album_gain": @(replayGainAlbumGain),
@"replayGainTrackGain": @(replayGainTrackGain), @"replaygain_track_gain": @(replayGainTrackGain),
@"codec": @"Opus", @"codec": @"Opus",
@"endian": @"host", @"endian": @"host",
@"encoding": @"lossy" }; @"encoding": @"lossy" };
} }
- (NSDictionary *)metadata { - (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 { + (NSArray *)fileTypes {

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 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 */; }; 836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */; };
8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8375B03F17FFEA400092A79F /* Cocoa.framework */; }; 8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8375B03F17FFEA400092A79F /* Cocoa.framework */; };
@ -27,6 +28,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference 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>"; }; 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>"; }; 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>"; }; 833F68411CDBCABC00AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@ -53,6 +55,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */, 8375B04017FFEA400092A79F /* Cocoa.framework in Frameworks */,
8301C14928780C1A00651A6E /* libFLAC.8.dylib in Frameworks */,
836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */, 836EF0CF27BB952F00BF35B2 /* libopusfile.0.dylib in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -90,6 +93,7 @@
8375B03E17FFEA400092A79F /* Frameworks */ = { 8375B03E17FFEA400092A79F /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8301C14828780C1A00651A6E /* libFLAC.8.dylib */,
836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */, 836EF0CE27BB952F00BF35B2 /* libopusfile.0.dylib */,
8375B03F17FFEA400092A79F /* Cocoa.framework */, 8375B03F17FFEA400092A79F /* Cocoa.framework */,
8375B04117FFEA400092A79F /* Other Frameworks */, 8375B04117FFEA400092A79F /* Other Frameworks */,
@ -340,10 +344,14 @@
../../ThirdParty/opusfile/include, ../../ThirdParty/opusfile/include,
../../ThirdParty/opus/include, ../../ThirdParty/opus/include,
../../ThirdParty/ogg/include, ../../ThirdParty/ogg/include,
../../ThirdParty/flac/include,
); );
INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist"; INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 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_BUNDLE_IDENTIFIER = net.kode54.opus;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
@ -364,10 +372,14 @@
../../ThirdParty/opusfile/include, ../../ThirdParty/opusfile/include,
../../ThirdParty/opus/include, ../../ThirdParty/opus/include,
../../ThirdParty/ogg/include, ../../ThirdParty/ogg/include,
../../ThirdParty/flac/include,
); );
INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist"; INFOPLIST_FILE = "Opus/OpusPlugin-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 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_BUNDLE_IDENTIFIER = net.kode54.opus;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;

View File

@ -87,10 +87,10 @@
rgAlbumPeak = tag->rgAlbumPeak(); rgAlbumPeak = tag->rgAlbumPeak();
rgTrackGain = tag->rgTrackGain(); rgTrackGain = tag->rgTrackGain();
rgTrackPeak = tag->rgTrackPeak(); rgTrackPeak = tag->rgTrackPeak();
[dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"]; [dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"];
[dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"]; [dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"];
[dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"]; [dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"];
[dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"]; [dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"];
soundcheck = tag->soundcheck(); soundcheck = tag->soundcheck();
if(!soundcheck.isEmpty()) { if(!soundcheck.isEmpty()) {
@ -128,6 +128,9 @@
if(!cuesheet.isEmpty()) if(!cuesheet.isEmpty())
[dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"]; [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. // Try to load the image.
NSData *image = nil; NSData *image = nil;

View File

@ -101,10 +101,10 @@
rgAlbumPeak = tag->rgAlbumPeak(); rgAlbumPeak = tag->rgAlbumPeak();
rgTrackGain = tag->rgTrackGain(); rgTrackGain = tag->rgTrackGain();
rgTrackPeak = tag->rgTrackPeak(); rgTrackPeak = tag->rgTrackPeak();
[dict setObject:@(rgAlbumGain) forKey:@"replayGainAlbumGain"]; [dict setObject:@(rgAlbumGain) forKey:@"replaygain_album_gain"];
[dict setObject:@(rgAlbumPeak) forKey:@"replayGainAlbumPeak"]; [dict setObject:@(rgAlbumPeak) forKey:@"replaygain_album_peak"];
[dict setObject:@(rgTrackGain) forKey:@"replayGainTrackGain"]; [dict setObject:@(rgTrackGain) forKey:@"replaygain_track_gain"];
[dict setObject:@(rgTrackPeak) forKey:@"replayGainTrackPeak"]; [dict setObject:@(rgTrackPeak) forKey:@"replaygain_track_peak"];
soundcheck = tag->soundcheck(); soundcheck = tag->soundcheck();
if(!soundcheck.isEmpty()) { if(!soundcheck.isEmpty()) {
@ -141,6 +141,9 @@
if(!cuesheet.isEmpty()) if(!cuesheet.isEmpty())
[dict setObject:[NSString stringWithUTF8String:cuesheet.toCString(true)] forKey:@"cuesheet"]; [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. // Try to load the image.

View File

@ -32,23 +32,8 @@
float frequency; float frequency;
long totalFrames; long totalFrames;
NSString *artist; NSDictionary *metaDict;
NSString *albumartist; NSDictionary *icyMetaDict;
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;
NSData *albumArt; NSData *albumArt;
} }

View File

@ -16,6 +16,8 @@
#import "NSDictionary+Merge.h" #import "NSDictionary+Merge.h"
#import <FLAC/metadata.h>
@implementation VorbisDecoder @implementation VorbisDecoder
static const int MAXCHANNELS = 8; static const int MAXCHANNELS = 8;
@ -101,111 +103,58 @@ long sourceTell(void *datasource) {
[self willChangeValueForKey:@"properties"]; [self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"]; [self didChangeValueForKey:@"properties"];
artist = @""; metaDict = [NSDictionary dictionary];
albumartist = @""; icyMetaDict = [NSDictionary dictionary];
album = @"";
title = @"";
genre = @"";
icygenre = @"";
icyalbum = @"";
icyartist = @"";
icytitle = @"";
year = @(0);
track = @(0);
disc = @(0);
albumArt = [NSData data]; albumArt = [NSData data];
[self updateMetadata]; [self updateMetadata];
return YES; return YES;
} }
- (NSString *)parseTag:(NSString *)tag fromTags:(vorbis_comment *)tags { static void setDictionary(NSMutableDictionary *dict, NSString *tag, NSString *value) {
NSMutableArray *tagStrings = [[NSMutableArray alloc] init]; NSMutableArray *array = [dict valueForKey:tag];
if(!array) {
int tagCount = vorbis_comment_query_count(tags, [tag UTF8String]); array = [[NSMutableArray alloc] init];
[dict setObject:array forKey:tag];
for(int i = 0; i < tagCount; ++i) {
const char *value = vorbis_comment_query(tags, [tag UTF8String], i);
[tagStrings addObject:guess_encoding_of_string(value)];
} }
[array addObject:value];
return [tagStrings componentsJoinedByString:@", "];
} }
- (void)updateMetadata { - (void)updateMetadata {
vorbis_comment *tags = ov_comment(&vorbisRef, -1); vorbis_comment *tags = ov_comment(&vorbisRef, -1);
NSMutableDictionary *_metaDict = [[NSMutableDictionary alloc] init];
NSData *_albumArt = albumArt;
if(tags) { if(tags) {
NSString *_artist = [self parseTag:@"artist" fromTags:tags]; for(int i = 0; i < tags->comments; ++i) {
NSString *_albumartist = [self parseTag:@"albumartist" fromTags:tags]; FLAC__StreamMetadata_VorbisComment_Entry entry = { .entry = (FLAC__byte *)tags->user_comments[i], .length = tags->comment_lengths[i] };
NSString *_album = [self parseTag:@"album" fromTags:tags]; char *name, *value;
NSString *_title = [self parseTag:@"title" fromTags:tags]; if(FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(entry, &name, &value)) {
NSString *_genre = [self parseTag:@"genre" fromTags:tags]; 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]; tagName = [tagName lowercaseString];
NSString *_yearYear = [self parseTag:@"year" fromTags:tags];
NSNumber *_year = @(0); if([tagName isEqualToString:@"metadata_block_picture"]) {
if([_yearDate length]) flac_picture_t *picture = flac_picture_parse_from_base64([tagValue UTF8String]);
_year = @([_yearDate intValue]); if(picture) {
else if([_yearYear length]) if(picture->binary && picture->binary_length) {
_year = @([_yearYear intValue]); _albumArt = [NSData dataWithBytes:picture->binary length:picture->binary_length];
}
NSString *_trackNumber = [self parseTag:@"tracknumber" fromTags:tags]; flac_picture_free(picture);
NSString *_trackNum = [self parseTag:@"tracknum" fromTags:tags]; }
NSString *_trackTrack = [self parseTag:@"track" fromTags:tags]; } else {
setDictionary(_metaDict, tagName, tagValue);
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];
} }
flac_picture_free(picture);
} }
} }
if(![_artist isEqual:artist] || if(![_albumArt isEqualToData:albumArt] ||
![_albumartist isEqual:albumartist] || ![_metaDict isEqualToDictionary:metaDict]) {
![_album isEqual:album] || metaDict = _metaDict;
![_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;
albumArt = _albumArt; albumArt = _albumArt;
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
@ -217,31 +166,35 @@ long sourceTell(void *datasource) {
- (void)updateIcyMetadata { - (void)updateIcyMetadata {
if([source seekable]) return; if([source seekable]) return;
NSString *_genre = icygenre; NSMutableDictionary *_icyMetaDict = [[NSMutableDictionary alloc] init];
NSString *_album = icyalbum;
NSString *_artist = icyartist;
NSString *_title = icytitle;
Class sourceClass = [source class]; Class sourceClass = [source class];
if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) { if([sourceClass isEqual:NSClassFromString(@"HTTPSource")]) {
HTTPSource *httpSource = (HTTPSource *)source; HTTPSource *httpSource = (HTTPSource *)source;
if([httpSource hasMetadata]) { if([httpSource hasMetadata]) {
NSDictionary *metadata = [httpSource metadata]; NSDictionary *metadata = [httpSource metadata];
_genre = [metadata valueForKey:@"genre"]; NSString *_genre = [metadata valueForKey:@"genre"];
_album = [metadata valueForKey:@"album"]; NSString *_album = [metadata valueForKey:@"album"];
_artist = [metadata valueForKey:@"artist"]; NSString *_artist = [metadata valueForKey:@"artist"];
_title = [metadata valueForKey:@"title"]; 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] || if(![_icyMetaDict isEqualToDictionary:icyMetaDict]) {
![_album isEqual:icyalbum] || icyMetaDict = _icyMetaDict;
![_artist isEqual:icyartist] ||
![_title isEqual:icytitle]) {
icygenre = _genre;
icyalbum = _album;
icyartist = _artist;
icytitle = _title;
[self willChangeValueForKey:@"metadata"]; [self willChangeValueForKey:@"metadata"];
[self didChangeValueForKey:@"metadata"]; [self didChangeValueForKey:@"metadata"];
} }
@ -325,7 +278,10 @@ long sourceTell(void *datasource) {
} }
- (NSDictionary *)metadata { - (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 { + (NSArray *)fileTypes {

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
17C93D360B8FDA66008627D6 /* VorbisDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93D340B8FDA66008627D6 /* VorbisDecoder.m */; }; 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 */; }; 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 */; }; 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 */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8301C14B287810F300651A6E /* libFLAC.8.dylib in Frameworks */,
8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */, 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */,
836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */, 836EF0DF27BB987000BF35B2 /* libvorbis.0.dylib in Frameworks */,
836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */, 836EF0D627BB969D00BF35B2 /* libvorbisfile.3.dylib in Frameworks */,
@ -103,6 +106,7 @@
1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */ = { 1058C7ACFEA557BF11CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8301C14A287810F300651A6E /* libFLAC.8.dylib */,
836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */, 836EF0DE27BB987000BF35B2 /* libvorbis.0.dylib */,
836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */, 836EF0D427BB969D00BF35B2 /* libvorbisfile.3.dylib */,
1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */, 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */,
@ -295,10 +299,14 @@
"$(inherited)", "$(inherited)",
../../ThirdParty/vorbis/include, ../../ThirdParty/vorbis/include,
../../ThirdParty/ogg/include, ../../ThirdParty/ogg/include,
../../ThirdParty/flac/include,
); );
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Library/Bundles"; 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_BUNDLE_IDENTIFIER = org.cogx.vorbis;
PRODUCT_NAME = VorbisPlugin; PRODUCT_NAME = VorbisPlugin;
SDKROOT = macosx; SDKROOT = macosx;
@ -324,10 +332,14 @@
"$(inherited)", "$(inherited)",
../../ThirdParty/vorbis/include, ../../ThirdParty/vorbis/include,
../../ThirdParty/ogg/include, ../../ThirdParty/ogg/include,
../../ThirdParty/flac/include,
); );
INFOPLIST_FILE = Info.plist; INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Library/Bundles"; 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_BUNDLE_IDENTIFIER = org.cogx.vorbis;
PRODUCT_NAME = VorbisPlugin; PRODUCT_NAME = VorbisPlugin;
SDKROOT = macosx; SDKROOT = macosx;

View File

@ -157,10 +157,10 @@ static NSString *get_description_tag(const char *description, const char *tag, c
@"floatingPoint": @(NO), @"floatingPoint": @(NO),
@"channels": @(channels), @"channels": @(channels),
@"seekable": @(YES), @"seekable": @(YES),
@"replayGainAlbumGain": rgAlbumGain, @"replaygain_album_gain": rgAlbumGain,
@"replayGainAlbumPeak": rgAlbumPeak, @"replaygain_album_peak": rgAlbumPeak,
@"replayGainTrackGain": rgTrackGain, @"replaygain_track_gain": rgTrackGain,
@"replayGainTrackPeak": rgTrackPeak, @"replaygain_track_peak": rgTrackPeak,
@"codec": codec, @"codec": codec,
@"endian": @"host", @"endian": @"host",
@"encoding": @"lossy/lossless" }; @"encoding": @"lossy/lossless" };

View File

@ -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 { - (NSDictionary *)coalesceEntryInfo:(NSDictionary *)entryInfo {
if(entryInfo == nil) return entryInfo; if(entryInfo == nil) return entryInfo;
@ -63,6 +74,9 @@
} else if([obj isKindOfClass:[NSData class]]) { } else if([obj isKindOfClass:[NSData class]]) {
NSData *dataObj = (NSData *)obj; NSData *dataObj = (NSData *)obj;
[ret setObject:[self coalesceArt:dataObj] forKey:key]; [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 { } else {
[ret setObject:obj forKey:key]; [ret setObject:obj forKey:key];
} }

View File

@ -14,9 +14,6 @@
/* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */ /* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */
"16.title" = "Length:"; "16.title" = "Length:";
/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */
"18.title" = "Year:";
/* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */ /* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */
"20.title" = "Genre:"; "20.title" = "Genre:";
@ -79,3 +76,9 @@
/* Class = "NSTextFieldCell"; title = "Codec:"; ObjectID = "QPg-Mb-Urn"; */ /* Class = "NSTextFieldCell"; title = "Codec:"; ObjectID = "QPg-Mb-Urn"; */
"QPg-Mb-Urn.title" = "Codec:"; "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:";

View File

@ -16,9 +16,6 @@
/* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */ /* Class = "NSTextFieldCell"; title = "Length:"; ObjectID = "16"; */
"16.title" = "Duración:"; "16.title" = "Duración:";
/* Class = "NSTextFieldCell"; title = "Year:"; ObjectID = "18"; */
"18.title" = "Año:";
/* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */ /* Class = "NSTextFieldCell"; title = "Genre:"; ObjectID = "20"; */
"20.title" = "Género:"; "20.title" = "Género:";
@ -109,3 +106,8 @@
/* Class = "NSTextFieldCell"; title = "N/A"; ObjectID = "Yby-OU-cqP"; */ /* Class = "NSTextFieldCell"; title = "N/A"; ObjectID = "Yby-OU-cqP"; */
"Yby-OU-cqP.title" = "n/d"; "Yby-OU-cqP.title" = "n/d";
/* Class = "NSTextFieldCell"; title = "Date:"; ObjectID = "17"; */
"18.title" = "Date:";
/* Class = "NSTextFieldCell"; title = "Comment:"; ObjectID = "cd3-Qt-hCm"; */
"Ule-N3-dKW.title" = "Comment:";