@naxodev/opencode-music-player 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +135 -53
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,14 @@ var CONVERSION_TIMEOUT_MS = 3000;
|
|
|
32
32
|
function imageDimensionsAreSafe(width, height) {
|
|
33
33
|
return Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0 && width <= MAX_IMAGE_DIMENSION && height <= MAX_IMAGE_DIMENSION && width * height <= MAX_IMAGE_PIXELS;
|
|
34
34
|
}
|
|
35
|
+
function pngDimensions(base64) {
|
|
36
|
+
const header = Buffer.from(base64.slice(0, 44), "base64");
|
|
37
|
+
if (header.length < 24 || header.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a" || header.readUInt32BE(8) !== 13 || header.toString("ascii", 12, 16) !== "IHDR")
|
|
38
|
+
return null;
|
|
39
|
+
const width = header.readUInt32BE(16);
|
|
40
|
+
const height = header.readUInt32BE(20);
|
|
41
|
+
return imageDimensionsAreSafe(width, height) ? { width, height } : null;
|
|
42
|
+
}
|
|
35
43
|
async function runCommandWithTimeout(command, timeoutMs) {
|
|
36
44
|
const proc = Bun.spawn(command, { stdout: "pipe", stderr: "ignore" });
|
|
37
45
|
let timedOut = false;
|
|
@@ -54,7 +62,7 @@ function dimensionsFromSips(output) {
|
|
|
54
62
|
const height = Number(output.match(/pixelHeight:\s*(\d+)/)?.[1]);
|
|
55
63
|
return imageDimensionsAreSafe(width, height) ? { width, height } : null;
|
|
56
64
|
}
|
|
57
|
-
async function
|
|
65
|
+
async function fitPng(bytes, size) {
|
|
58
66
|
if (bytes.byteLength === 0 || bytes.byteLength > MAX_ARTWORK_BYTES)
|
|
59
67
|
return null;
|
|
60
68
|
const dir = await mkdtemp(join(tmpdir(), "opencode-music-art-"));
|
|
@@ -63,14 +71,18 @@ async function squarePng(bytes, size) {
|
|
|
63
71
|
try {
|
|
64
72
|
await Bun.write(input, bytes);
|
|
65
73
|
const inspected = await runCommandWithTimeout(["sips", "-g", "pixelWidth", "-g", "pixelHeight", input], CONVERSION_TIMEOUT_MS);
|
|
66
|
-
|
|
74
|
+
const dimensions = dimensionsFromSips(inspected.out);
|
|
75
|
+
if (inspected.timed_out || inspected.code !== 0 || !dimensions) {
|
|
67
76
|
return null;
|
|
68
77
|
}
|
|
78
|
+
const scale = size / Math.max(dimensions.width, dimensions.height);
|
|
79
|
+
const width = Math.max(1, Math.round(dimensions.width * scale));
|
|
80
|
+
const height = Math.max(1, Math.round(dimensions.height * scale));
|
|
69
81
|
const converted = await runCommandWithTimeout([
|
|
70
82
|
"sips",
|
|
71
83
|
"-z",
|
|
72
|
-
String(
|
|
73
|
-
String(
|
|
84
|
+
String(height),
|
|
85
|
+
String(width),
|
|
74
86
|
"-s",
|
|
75
87
|
"format",
|
|
76
88
|
"png",
|
|
@@ -134,8 +146,8 @@ async function resolveArtworkDetails(id, target, nativeBase64, legacyId = id, fe
|
|
|
134
146
|
}
|
|
135
147
|
for (const bytes of candidates) {
|
|
136
148
|
try {
|
|
137
|
-
const nativePng = await
|
|
138
|
-
const thumbnail = nativePng ? await
|
|
149
|
+
const nativePng = await fitPng(bytes, 300);
|
|
150
|
+
const thumbnail = nativePng ? await fitPng(nativePng, 24) : null;
|
|
139
151
|
if (nativePng && thumbnail) {
|
|
140
152
|
return {
|
|
141
153
|
artwork: {
|
|
@@ -162,8 +174,8 @@ async function resolveArtworkDetails(id, target, nativeBase64, legacyId = id, fe
|
|
|
162
174
|
return { artwork: null, duration_ms: catalog.duration_ms };
|
|
163
175
|
}
|
|
164
176
|
try {
|
|
165
|
-
const nativePng = await
|
|
166
|
-
const thumbnail = nativePng ? await
|
|
177
|
+
const nativePng = await fitPng(catalog.bytes, 300);
|
|
178
|
+
const thumbnail = nativePng ? await fitPng(nativePng, 24) : null;
|
|
167
179
|
if (!nativePng || !thumbnail) {
|
|
168
180
|
return { artwork: null, duration_ms: catalog.duration_ms };
|
|
169
181
|
}
|
|
@@ -709,7 +721,7 @@ import { use as _$use3 } from "@opentui/solid";
|
|
|
709
721
|
import { setProp as _$setProp2 } from "@opentui/solid";
|
|
710
722
|
import { createElement as _$createElement3 } from "@opentui/solid";
|
|
711
723
|
import { createComponent as _$createComponent2 } from "@opentui/solid";
|
|
712
|
-
import { Show, createEffect, createMemo, createSignal, onCleanup as onCleanup3, onMount as onMount3 } from "solid-js";
|
|
724
|
+
import { Show as Show2, createEffect, createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup3, onMount as onMount3 } from "solid-js";
|
|
713
725
|
import { MouseButton } from "@opentui/core";
|
|
714
726
|
|
|
715
727
|
// artwork.tsx
|
|
@@ -721,9 +733,49 @@ import { createComponent as _$createComponent } from "@opentui/solid";
|
|
|
721
733
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
722
734
|
import { use as _$use } from "@opentui/solid";
|
|
723
735
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
724
|
-
import { For, onCleanup, onMount } from "solid-js";
|
|
736
|
+
import { For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js";
|
|
725
737
|
|
|
726
738
|
// artwork-placement.ts
|
|
739
|
+
function fitNativeArtworkGeometry(slot, image, cell) {
|
|
740
|
+
if ([
|
|
741
|
+
slot.width,
|
|
742
|
+
slot.height,
|
|
743
|
+
image.width,
|
|
744
|
+
image.height,
|
|
745
|
+
cell.width,
|
|
746
|
+
cell.height
|
|
747
|
+
].some((value) => !Number.isFinite(value) || value <= 0))
|
|
748
|
+
return null;
|
|
749
|
+
const width = Math.floor(slot.width);
|
|
750
|
+
const height = Math.floor(slot.height);
|
|
751
|
+
if (width < 1 || height < 1)
|
|
752
|
+
return null;
|
|
753
|
+
const pixelWidth = width * cell.width;
|
|
754
|
+
const pixelHeight = height * cell.height;
|
|
755
|
+
const aspect = image.width / image.height;
|
|
756
|
+
if (pixelWidth / aspect <= pixelHeight) {
|
|
757
|
+
const displayedHeight = Math.round(pixelWidth / aspect);
|
|
758
|
+
if (displayedHeight < 1)
|
|
759
|
+
return null;
|
|
760
|
+
const occupiedRows = Math.ceil(displayedHeight / cell.height);
|
|
761
|
+
return {
|
|
762
|
+
x: slot.x,
|
|
763
|
+
y: slot.y + Math.floor((height - occupiedRows) / 2),
|
|
764
|
+
width,
|
|
765
|
+
height: 0
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
const displayedWidth = Math.round(pixelHeight * aspect);
|
|
769
|
+
if (displayedWidth < 1)
|
|
770
|
+
return null;
|
|
771
|
+
const occupiedColumns = Math.ceil(displayedWidth / cell.width);
|
|
772
|
+
return {
|
|
773
|
+
x: slot.x + Math.floor((width - occupiedColumns) / 2),
|
|
774
|
+
y: slot.y,
|
|
775
|
+
width: 0,
|
|
776
|
+
height
|
|
777
|
+
};
|
|
778
|
+
}
|
|
727
779
|
var CLEARED_STATE = {
|
|
728
780
|
transmitted: 0,
|
|
729
781
|
placement: null
|
|
@@ -798,6 +850,9 @@ function planNativeArtworkPlacement(input) {
|
|
|
798
850
|
var APC = "\x1B_G";
|
|
799
851
|
var ST = "\x1B\\";
|
|
800
852
|
var CHUNK_SIZE = 4096;
|
|
853
|
+
function scalingDimensions(width, height) {
|
|
854
|
+
return [width > 0 ? `c=${width}` : "", height > 0 ? `r=${height}` : ""].filter(Boolean).join(",");
|
|
855
|
+
}
|
|
801
856
|
function tmuxPassthrough(data) {
|
|
802
857
|
return `\x1BPtmux;${data.replaceAll("\x1B", "\x1B\x1B")}\x1B\\`;
|
|
803
858
|
}
|
|
@@ -816,13 +871,13 @@ function kittyDisplayPng(pngBase64, imageId, x, y, width, height) {
|
|
|
816
871
|
return chunks.map((command, index) => {
|
|
817
872
|
let next = command;
|
|
818
873
|
if (index === 0) {
|
|
819
|
-
next = next.replace(`${APC}a=t,f=100,i=${imageId},q=2,`, `\x1B7\x1B[${y + 1};${x + 1}H${APC}a=T,f=100,i=${imageId},p=${imageId},q=2,C=1
|
|
874
|
+
next = next.replace(`${APC}a=t,f=100,i=${imageId},q=2,`, `\x1B7\x1B[${y + 1};${x + 1}H${APC}a=T,f=100,i=${imageId},p=${imageId},q=2,C=1,${scalingDimensions(width, height)},z=1,`);
|
|
820
875
|
}
|
|
821
876
|
return index === chunks.length - 1 ? `${next}\x1B8` : next;
|
|
822
877
|
});
|
|
823
878
|
}
|
|
824
879
|
function kittyPlace(imageId, placementId, x, y, width, height) {
|
|
825
|
-
return `\x1B7\x1B[${y + 1};${x + 1}H${APC}a=p,i=${imageId},p=${placementId},q=2
|
|
880
|
+
return `\x1B7\x1B[${y + 1};${x + 1}H${APC}a=p,i=${imageId},p=${placementId},q=2,${scalingDimensions(width, height)},z=1;${ST}\x1B8`;
|
|
826
881
|
}
|
|
827
882
|
function kittyDelete(imageId) {
|
|
828
883
|
return `${APC}a=d,d=I,i=${imageId},q=2;${ST}`;
|
|
@@ -986,6 +1041,8 @@ function AlbumArtwork(props) {
|
|
|
986
1041
|
let container;
|
|
987
1042
|
let paintPending = false;
|
|
988
1043
|
let disposed = false;
|
|
1044
|
+
const [nativeVisible, setNativeVisible] = createSignal(false);
|
|
1045
|
+
const imageDimensions = createMemo(() => pngDimensions(props.artwork.png_base64));
|
|
989
1046
|
const applyAction = (action) => {
|
|
990
1047
|
const renderer2 = props.context.renderer;
|
|
991
1048
|
switch (action.type) {
|
|
@@ -1034,23 +1091,39 @@ function AlbumArtwork(props) {
|
|
|
1034
1091
|
const imageId = imageIdForArtwork(props.artwork.id);
|
|
1035
1092
|
const x = slot ? slot.screenX + offset.x : 0;
|
|
1036
1093
|
const y = slot ? slot.screenY + offset.y : 0;
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1094
|
+
const image = imageDimensions();
|
|
1095
|
+
const resolution = renderer.resolution;
|
|
1096
|
+
const geometry = slot && image && resolution ? fitNativeArtworkGeometry({
|
|
1097
|
+
x,
|
|
1098
|
+
y,
|
|
1099
|
+
width: slot.width,
|
|
1100
|
+
height: slot.height
|
|
1101
|
+
}, image, {
|
|
1102
|
+
width: Math.floor(resolution.width / renderer.terminalWidth),
|
|
1103
|
+
height: Math.floor(resolution.height / renderer.terminalHeight)
|
|
1104
|
+
}) : null;
|
|
1039
1105
|
const plan = planNativeArtworkPlacement({
|
|
1040
1106
|
state: runtime.state,
|
|
1041
1107
|
imageId,
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1108
|
+
...geometry ?? {
|
|
1109
|
+
x,
|
|
1110
|
+
y,
|
|
1111
|
+
width: 0,
|
|
1112
|
+
height: 0
|
|
1113
|
+
},
|
|
1046
1114
|
kittySupported,
|
|
1047
|
-
slotValid,
|
|
1115
|
+
slotValid: slotValid && geometry !== null,
|
|
1048
1116
|
disposed
|
|
1049
1117
|
});
|
|
1050
|
-
if (plan.actions.length === 0)
|
|
1118
|
+
if (plan.actions.length === 0) {
|
|
1119
|
+
setNativeVisible(runtime.state.placement !== null);
|
|
1051
1120
|
return;
|
|
1052
|
-
|
|
1121
|
+
}
|
|
1122
|
+
if (applyPlan(plan)) {
|
|
1053
1123
|
commitPlan(plan);
|
|
1124
|
+
setNativeVisible(plan.nextState.placement !== null);
|
|
1125
|
+
} else
|
|
1126
|
+
setNativeVisible(false);
|
|
1054
1127
|
};
|
|
1055
1128
|
const scheduleNativeImage = () => {
|
|
1056
1129
|
if (paintPending || disposed)
|
|
@@ -1088,27 +1161,36 @@ function AlbumArtwork(props) {
|
|
|
1088
1161
|
_$setProp(_el$, "width", 24);
|
|
1089
1162
|
_$setProp(_el$, "height", 12);
|
|
1090
1163
|
_$setProp(_el$, "flexDirection", "column");
|
|
1164
|
+
_$setProp(_el$, "alignItems", "center");
|
|
1165
|
+
_$setProp(_el$, "justifyContent", "center");
|
|
1091
1166
|
_$setProp(_el$, "overflow", "hidden");
|
|
1092
|
-
_$insert(_el$, _$createComponent(
|
|
1093
|
-
get
|
|
1094
|
-
return
|
|
1167
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
1168
|
+
get when() {
|
|
1169
|
+
return !nativeVisible();
|
|
1095
1170
|
},
|
|
1096
|
-
children
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
_$
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1171
|
+
get children() {
|
|
1172
|
+
return _$createComponent(For, {
|
|
1173
|
+
get each() {
|
|
1174
|
+
return props.artwork.cells;
|
|
1175
|
+
},
|
|
1176
|
+
children: (row) => (() => {
|
|
1177
|
+
var _el$2 = _$createElement("text");
|
|
1178
|
+
_$insert(_el$2, _$createComponent(For, {
|
|
1179
|
+
each: row,
|
|
1180
|
+
children: (cell) => (() => {
|
|
1181
|
+
var _el$3 = _$createElement("span");
|
|
1182
|
+
_$insertNode(_el$3, _$createTextNode(`\u2580`));
|
|
1183
|
+
_$effect((_$p) => _$setProp(_el$3, "style", {
|
|
1184
|
+
fg: cell.upper,
|
|
1185
|
+
bg: cell.lower
|
|
1186
|
+
}, _$p));
|
|
1187
|
+
return _el$3;
|
|
1188
|
+
})()
|
|
1189
|
+
}));
|
|
1190
|
+
return _el$2;
|
|
1108
1191
|
})()
|
|
1109
|
-
})
|
|
1110
|
-
|
|
1111
|
-
})()
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1112
1194
|
}));
|
|
1113
1195
|
return _el$;
|
|
1114
1196
|
})();
|
|
@@ -1376,7 +1458,7 @@ function CompactPlayer(props) {
|
|
|
1376
1458
|
let row;
|
|
1377
1459
|
let content;
|
|
1378
1460
|
let allocatedWidth = Number.MAX_SAFE_INTEGER;
|
|
1379
|
-
const track =
|
|
1461
|
+
const track = createMemo2(() => props.state.player?.track);
|
|
1380
1462
|
const renderLine = () => {
|
|
1381
1463
|
const current = track();
|
|
1382
1464
|
if (!current)
|
|
@@ -1399,7 +1481,7 @@ function CompactPlayer(props) {
|
|
|
1399
1481
|
if (row?.onSizeChange === updateWidth)
|
|
1400
1482
|
row.onSizeChange = undefined;
|
|
1401
1483
|
});
|
|
1402
|
-
return _$createComponent2(
|
|
1484
|
+
return _$createComponent2(Show2, {
|
|
1403
1485
|
get when() {
|
|
1404
1486
|
return track();
|
|
1405
1487
|
},
|
|
@@ -1580,8 +1662,8 @@ function LiveElapsed(props) {
|
|
|
1580
1662
|
})();
|
|
1581
1663
|
}
|
|
1582
1664
|
function IconBtn(props) {
|
|
1583
|
-
const [hover, setHover] =
|
|
1584
|
-
const [press, setPress] =
|
|
1665
|
+
const [hover, setHover] = createSignal2(false);
|
|
1666
|
+
const [press, setPress] = createSignal2(false);
|
|
1585
1667
|
const bg = () => {
|
|
1586
1668
|
if (press() || props.active) {
|
|
1587
1669
|
return props.theme.background.action.primary.base;
|
|
@@ -1659,10 +1741,10 @@ function SidebarPlayer(props) {
|
|
|
1659
1741
|
unsubscribeSession?.();
|
|
1660
1742
|
presentationListeners.clear();
|
|
1661
1743
|
});
|
|
1662
|
-
const player =
|
|
1663
|
-
const track =
|
|
1664
|
-
const playing =
|
|
1665
|
-
const duration =
|
|
1744
|
+
const player = createMemo2(() => props.state.player);
|
|
1745
|
+
const track = createMemo2(() => player()?.track);
|
|
1746
|
+
const playing = createMemo2(() => !!player()?.is_playing);
|
|
1747
|
+
const duration = createMemo2(() => presentationDuration(track()));
|
|
1666
1748
|
return (() => {
|
|
1667
1749
|
var _el$1 = _$createElement3("box"), _el$10 = _$createElement3("box"), _el$11 = _$createElement3("box"), _el$12 = _$createElement3("box"), _el$16 = _$createElement3("box");
|
|
1668
1750
|
_$insertNode2(_el$1, _el$10);
|
|
@@ -1676,7 +1758,7 @@ function SidebarPlayer(props) {
|
|
|
1676
1758
|
_$setProp2(_el$1, "paddingBottom", 1);
|
|
1677
1759
|
_$setProp2(_el$1, "gap", 1);
|
|
1678
1760
|
_$setProp2(_el$1, "flexShrink", 0);
|
|
1679
|
-
_$insert3(_el$1, _$createComponent2(
|
|
1761
|
+
_$insert3(_el$1, _$createComponent2(Show2, {
|
|
1680
1762
|
get when() {
|
|
1681
1763
|
return props.state.error;
|
|
1682
1764
|
},
|
|
@@ -1693,7 +1775,7 @@ function SidebarPlayer(props) {
|
|
|
1693
1775
|
_$setProp2(_el$11, "width", 24);
|
|
1694
1776
|
_$setProp2(_el$11, "height", 12);
|
|
1695
1777
|
_$setProp2(_el$11, "overflow", "hidden");
|
|
1696
|
-
_$insert3(_el$11, _$createComponent2(
|
|
1778
|
+
_$insert3(_el$11, _$createComponent2(Show2, {
|
|
1697
1779
|
get when() {
|
|
1698
1780
|
return track()?.artwork;
|
|
1699
1781
|
},
|
|
@@ -1722,7 +1804,7 @@ function SidebarPlayer(props) {
|
|
|
1722
1804
|
}
|
|
1723
1805
|
})
|
|
1724
1806
|
}));
|
|
1725
|
-
_$insert3(_el$1, _$createComponent2(
|
|
1807
|
+
_$insert3(_el$1, _$createComponent2(Show2, {
|
|
1726
1808
|
get when() {
|
|
1727
1809
|
return track();
|
|
1728
1810
|
},
|
|
@@ -1741,7 +1823,7 @@ function SidebarPlayer(props) {
|
|
|
1741
1823
|
_$setProp2(_el$21, "gap", 0);
|
|
1742
1824
|
_$insertNode2(_el$22, _el$23);
|
|
1743
1825
|
_$insert3(_el$23, () => sanitizeTerminalText(t2().name, "Unknown track"));
|
|
1744
|
-
_$insert3(_el$21, _$createComponent2(
|
|
1826
|
+
_$insert3(_el$21, _$createComponent2(Show2, {
|
|
1745
1827
|
get when() {
|
|
1746
1828
|
return t2().artists;
|
|
1747
1829
|
},
|
|
@@ -1752,7 +1834,7 @@ function SidebarPlayer(props) {
|
|
|
1752
1834
|
return _el$24;
|
|
1753
1835
|
}
|
|
1754
1836
|
}), null);
|
|
1755
|
-
_$insert3(_el$21, _$createComponent2(
|
|
1837
|
+
_$insert3(_el$21, _$createComponent2(Show2, {
|
|
1756
1838
|
get when() {
|
|
1757
1839
|
return t2().album;
|
|
1758
1840
|
},
|
|
@@ -1778,7 +1860,7 @@ function SidebarPlayer(props) {
|
|
|
1778
1860
|
bars: 24,
|
|
1779
1861
|
variant: "hero"
|
|
1780
1862
|
}));
|
|
1781
|
-
_$insert3(_el$1, _$createComponent2(
|
|
1863
|
+
_$insert3(_el$1, _$createComponent2(Show2, {
|
|
1782
1864
|
get when() {
|
|
1783
1865
|
return _$memo(() => !!track())() && duration() > 0;
|
|
1784
1866
|
},
|