@maxedgar/yeet 1.0.0 → 1.1.0
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/cli.js +527 -160
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,9 @@ import { render } from "ink";
|
|
|
8
8
|
import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
9
9
|
import os3 from "os";
|
|
10
10
|
import path3 from "path";
|
|
11
|
-
import { Box as
|
|
11
|
+
import { Box as Box6, Text as Text8, useApp, useInput as useInput2, useStdout as useStdout3 } from "ink";
|
|
12
12
|
import SelectInput from "ink-select-input";
|
|
13
|
-
import
|
|
13
|
+
import Spinner2 from "ink-spinner";
|
|
14
14
|
|
|
15
15
|
// src/components/framed-input.tsx
|
|
16
16
|
import { Box, Text } from "ink";
|
|
@@ -283,20 +283,117 @@ function ProgressBar({ percent, width = 30 }) {
|
|
|
283
283
|
] });
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// src/components/queue-list.tsx
|
|
287
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
288
|
+
import Spinner from "ink-spinner";
|
|
289
|
+
|
|
290
|
+
// src/lib/format.ts
|
|
291
|
+
function formatBytes(bytes) {
|
|
292
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
293
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
294
|
+
let value = bytes;
|
|
295
|
+
let unit = 0;
|
|
296
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
297
|
+
value /= 1024;
|
|
298
|
+
unit++;
|
|
299
|
+
}
|
|
300
|
+
return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
301
|
+
}
|
|
302
|
+
function formatDuration(seconds) {
|
|
303
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
|
304
|
+
const s = Math.round(seconds);
|
|
305
|
+
const h = Math.floor(s / 3600);
|
|
306
|
+
const m = Math.floor(s % 3600 / 60);
|
|
307
|
+
const sec = s % 60;
|
|
308
|
+
const mm = h > 0 ? String(m).padStart(2, "0") : String(m);
|
|
309
|
+
const ss = String(sec).padStart(2, "0");
|
|
310
|
+
return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
311
|
+
}
|
|
312
|
+
function truncate(text, max) {
|
|
313
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
314
|
+
}
|
|
315
|
+
function shortenPath(filepath, homedir, max = 60) {
|
|
316
|
+
const pretty = filepath.startsWith(homedir) ? `~${filepath.slice(homedir.length)}` : filepath;
|
|
317
|
+
if (pretty.length <= max) return pretty;
|
|
318
|
+
const ext = /\.\w{1,5}$/.exec(pretty)?.[0] ?? "";
|
|
319
|
+
return `${pretty.slice(0, max - ext.length - 1)}\u2026${ext}`;
|
|
320
|
+
}
|
|
321
|
+
function wrapText(text, width) {
|
|
322
|
+
const lines = [];
|
|
323
|
+
let line = "";
|
|
324
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
325
|
+
if (!line) line = word;
|
|
326
|
+
else if (line.length + 1 + word.length <= width) line += ` ${word}`;
|
|
327
|
+
else {
|
|
328
|
+
lines.push(line);
|
|
329
|
+
line = word;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (line) lines.push(line);
|
|
333
|
+
return lines;
|
|
334
|
+
}
|
|
335
|
+
function formatSpeed(bytesPerSecond) {
|
|
336
|
+
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return "";
|
|
337
|
+
return `${formatBytes(bytesPerSecond)}/s`;
|
|
338
|
+
}
|
|
339
|
+
function formatEta(seconds) {
|
|
340
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
|
341
|
+
return formatDuration(seconds);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/components/queue-list.tsx
|
|
345
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
346
|
+
var MAX_VISIBLE = 7;
|
|
347
|
+
function StatusIcon({ status }) {
|
|
348
|
+
const theme = useTheme();
|
|
349
|
+
if (status === "active") {
|
|
350
|
+
return /* @__PURE__ */ jsx6(Text5, { color: theme.primary, children: /* @__PURE__ */ jsx6(Spinner, { type: "dots" }) });
|
|
351
|
+
}
|
|
352
|
+
if (status === "done") return /* @__PURE__ */ jsx6(Text5, { color: theme.primary, children: "\u2713" });
|
|
353
|
+
if (status === "error") return /* @__PURE__ */ jsx6(Text5, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u2717" });
|
|
354
|
+
if (status === "skipped") return /* @__PURE__ */ jsx6(Text5, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u292B" });
|
|
355
|
+
return /* @__PURE__ */ jsx6(Text5, { color: theme.gray, dimColor: theme.dimSecondary, children: "\xB7" });
|
|
356
|
+
}
|
|
357
|
+
function QueueList({ items, width }) {
|
|
358
|
+
const theme = useTheme();
|
|
359
|
+
if (items.length === 0) return null;
|
|
360
|
+
const activeIndex = items.findIndex((item) => item.status === "active");
|
|
361
|
+
const anchor = activeIndex === -1 ? items.length - 1 : activeIndex;
|
|
362
|
+
const start = Math.max(0, Math.min(anchor - Math.floor(MAX_VISIBLE / 2), Math.max(0, items.length - MAX_VISIBLE)));
|
|
363
|
+
const visible = items.slice(start, start + MAX_VISIBLE);
|
|
364
|
+
const hiddenAbove = start;
|
|
365
|
+
const hiddenBelow = Math.max(0, items.length - (start + visible.length));
|
|
366
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", width, children: [
|
|
367
|
+
hiddenAbove > 0 ? /* @__PURE__ */ jsx6(Text5, { color: theme.gray, dimColor: theme.dimSecondary, children: ` \u2191 ${hiddenAbove} more` }) : null,
|
|
368
|
+
visible.map((item) => /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
369
|
+
/* @__PURE__ */ jsx6(Box5, { width: 2, children: /* @__PURE__ */ jsx6(StatusIcon, { status: item.status }) }),
|
|
370
|
+
/* @__PURE__ */ jsx6(
|
|
371
|
+
Text5,
|
|
372
|
+
{
|
|
373
|
+
color: item.status === "done" || item.status === "active" ? theme.primary : theme.gray,
|
|
374
|
+
dimColor: item.status !== "done" && item.status !== "active" && theme.dimSecondary,
|
|
375
|
+
children: truncate(item.title || item.url, Math.max(4, width - 3))
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
] }, item.id)),
|
|
379
|
+
hiddenBelow > 0 ? /* @__PURE__ */ jsx6(Text5, { color: theme.gray, dimColor: theme.dimSecondary, children: ` \u2193 ${hiddenBelow} more` }) : null
|
|
380
|
+
] });
|
|
381
|
+
}
|
|
382
|
+
|
|
286
383
|
// src/components/shortcuts.tsx
|
|
287
|
-
import { Text as
|
|
288
|
-
import { Fragment, jsx as
|
|
384
|
+
import { Text as Text6 } from "ink";
|
|
385
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
289
386
|
function Shortcuts({ items, leading }) {
|
|
290
387
|
const theme = useTheme();
|
|
291
|
-
return /* @__PURE__ */
|
|
292
|
-
leading ? /* @__PURE__ */
|
|
388
|
+
return /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
389
|
+
leading ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
293
390
|
leading,
|
|
294
|
-
/* @__PURE__ */
|
|
391
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.gray, dimColor: theme.dimSecondary, children: " \xB7 " })
|
|
295
392
|
] }) : null,
|
|
296
|
-
items.map(([key, label], index) => /* @__PURE__ */
|
|
297
|
-
index > 0 ? /* @__PURE__ */
|
|
298
|
-
/* @__PURE__ */
|
|
299
|
-
/* @__PURE__ */
|
|
393
|
+
items.map(([key, label], index) => /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
394
|
+
index > 0 ? /* @__PURE__ */ jsx7(Text6, { color: theme.gray, dimColor: theme.dimSecondary, children: " \xB7 " }) : null,
|
|
395
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.primary, children: key }),
|
|
396
|
+
/* @__PURE__ */ jsxs5(Text6, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
300
397
|
" ",
|
|
301
398
|
label
|
|
302
399
|
] })
|
|
@@ -306,7 +403,7 @@ function Shortcuts({ items, leading }) {
|
|
|
306
403
|
|
|
307
404
|
// src/components/text-input.tsx
|
|
308
405
|
import { useRef as useRef2, useState as useState3 } from "react";
|
|
309
|
-
import { Text as
|
|
406
|
+
import { Text as Text7, useInput } from "ink";
|
|
310
407
|
|
|
311
408
|
// src/lib/use-mouse-click.ts
|
|
312
409
|
import { useEffect as useEffect3, useRef } from "react";
|
|
@@ -338,7 +435,7 @@ function useMouseClick(onClick, isActive) {
|
|
|
338
435
|
var stripMouseReports = (value) => value.replace(/\u001B?\[?<\d+;\d+;\d+[Mm]/g, "");
|
|
339
436
|
|
|
340
437
|
// src/components/text-input.tsx
|
|
341
|
-
import { jsx as
|
|
438
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
342
439
|
var wordLeft = (text, from) => {
|
|
343
440
|
let i = from;
|
|
344
441
|
while (i > 0 && !/\w/.test(text[i - 1])) i--;
|
|
@@ -466,18 +563,35 @@ function TextInput({
|
|
|
466
563
|
if (cursor > offset + span - 1) offset = cursor - span + 1;
|
|
467
564
|
offsetRef.current = offset;
|
|
468
565
|
if (!value) {
|
|
469
|
-
return /* @__PURE__ */
|
|
470
|
-
/* @__PURE__ */
|
|
471
|
-
/* @__PURE__ */
|
|
566
|
+
return /* @__PURE__ */ jsxs6(Text7, { children: [
|
|
567
|
+
/* @__PURE__ */ jsx8(Text7, { inverse: true, children: " " }),
|
|
568
|
+
/* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: placeholder.slice(0, span - 1) })
|
|
472
569
|
] });
|
|
473
570
|
}
|
|
474
571
|
const cells = Array.from({ length: Math.min(span, value.length - offset + 1) }, (_, column) => {
|
|
475
572
|
const index = offset + column;
|
|
476
573
|
const selected = selection !== null && index >= selection[0] && index < selection[1];
|
|
477
574
|
const atCursor = selection === null && index === cursor;
|
|
478
|
-
return /* @__PURE__ */
|
|
575
|
+
return /* @__PURE__ */ jsx8(Text7, { color: theme.primary, inverse: selected || atCursor, children: value[index] ?? " " }, index);
|
|
479
576
|
});
|
|
480
|
-
return /* @__PURE__ */
|
|
577
|
+
return /* @__PURE__ */ jsx8(Text7, { children: cells });
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/lib/batch.ts
|
|
581
|
+
function makeBatchItems(entries) {
|
|
582
|
+
return entries.map((entry, index) => ({
|
|
583
|
+
id: `${index}-${entry.url}`,
|
|
584
|
+
url: entry.url,
|
|
585
|
+
title: entry.title,
|
|
586
|
+
status: "pending"
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
function batchSummary(items) {
|
|
590
|
+
const done = items.filter((item) => item.status === "done").length;
|
|
591
|
+
const error = items.filter((item) => item.status === "error").length;
|
|
592
|
+
const skipped = items.filter((item) => item.status === "skipped").length;
|
|
593
|
+
const pending = items.filter((item) => item.status === "pending" || item.status === "active").length;
|
|
594
|
+
return { done, error, skipped, pending, total: items.length };
|
|
481
595
|
}
|
|
482
596
|
|
|
483
597
|
// src/lib/click-map.ts
|
|
@@ -531,60 +645,6 @@ function frameRowSpan(row) {
|
|
|
531
645
|
return [first + 1, line.trimEnd().length];
|
|
532
646
|
}
|
|
533
647
|
|
|
534
|
-
// src/lib/format.ts
|
|
535
|
-
function formatBytes(bytes) {
|
|
536
|
-
if (!Number.isFinite(bytes) || bytes <= 0) return "";
|
|
537
|
-
const units = ["B", "KB", "MB", "GB"];
|
|
538
|
-
let value = bytes;
|
|
539
|
-
let unit = 0;
|
|
540
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
541
|
-
value /= 1024;
|
|
542
|
-
unit++;
|
|
543
|
-
}
|
|
544
|
-
return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
545
|
-
}
|
|
546
|
-
function formatDuration(seconds) {
|
|
547
|
-
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
|
548
|
-
const s = Math.round(seconds);
|
|
549
|
-
const h = Math.floor(s / 3600);
|
|
550
|
-
const m = Math.floor(s % 3600 / 60);
|
|
551
|
-
const sec = s % 60;
|
|
552
|
-
const mm = h > 0 ? String(m).padStart(2, "0") : String(m);
|
|
553
|
-
const ss = String(sec).padStart(2, "0");
|
|
554
|
-
return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
555
|
-
}
|
|
556
|
-
function truncate(text, max) {
|
|
557
|
-
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
558
|
-
}
|
|
559
|
-
function shortenPath(filepath, homedir, max = 60) {
|
|
560
|
-
const pretty = filepath.startsWith(homedir) ? `~${filepath.slice(homedir.length)}` : filepath;
|
|
561
|
-
if (pretty.length <= max) return pretty;
|
|
562
|
-
const ext = /\.\w{1,5}$/.exec(pretty)?.[0] ?? "";
|
|
563
|
-
return `${pretty.slice(0, max - ext.length - 1)}\u2026${ext}`;
|
|
564
|
-
}
|
|
565
|
-
function wrapText(text, width) {
|
|
566
|
-
const lines = [];
|
|
567
|
-
let line = "";
|
|
568
|
-
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
569
|
-
if (!line) line = word;
|
|
570
|
-
else if (line.length + 1 + word.length <= width) line += ` ${word}`;
|
|
571
|
-
else {
|
|
572
|
-
lines.push(line);
|
|
573
|
-
line = word;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
if (line) lines.push(line);
|
|
577
|
-
return lines;
|
|
578
|
-
}
|
|
579
|
-
function formatSpeed(bytesPerSecond) {
|
|
580
|
-
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return "";
|
|
581
|
-
return `${formatBytes(bytesPerSecond)}/s`;
|
|
582
|
-
}
|
|
583
|
-
function formatEta(seconds) {
|
|
584
|
-
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
|
585
|
-
return formatDuration(seconds);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
648
|
// src/lib/history.ts
|
|
589
649
|
import fs from "fs";
|
|
590
650
|
import os from "os";
|
|
@@ -700,9 +760,17 @@ async function findFfmpeg() {
|
|
|
700
760
|
}
|
|
701
761
|
return void 0;
|
|
702
762
|
}
|
|
703
|
-
|
|
763
|
+
function resolveEntryUrl(entry) {
|
|
764
|
+
if (entry.webpage_url) return entry.webpage_url;
|
|
765
|
+
if (entry.url && /^https?:\/\//i.test(entry.url)) return entry.url;
|
|
766
|
+
if (entry.id && entry.ie_key?.toLowerCase().includes("youtube")) {
|
|
767
|
+
return `https://www.youtube.com/watch?v=${entry.id}`;
|
|
768
|
+
}
|
|
769
|
+
return entry.url;
|
|
770
|
+
}
|
|
771
|
+
async function probe(ytdlp, url, signal, depth = 0) {
|
|
704
772
|
const stdout = await new Promise((resolve, reject) => {
|
|
705
|
-
const child = spawn(ytdlp, ["-J", "--no-playlist", "--no-warnings", url], { signal });
|
|
773
|
+
const child = spawn(ytdlp, ["-J", "--no-playlist", "--flat-playlist", "--no-warnings", url], { signal });
|
|
706
774
|
let out = "";
|
|
707
775
|
let stderr = "";
|
|
708
776
|
child.stdout.on("data", (chunk) => out += chunk);
|
|
@@ -716,15 +784,29 @@ async function probe(ytdlp, url, signal) {
|
|
|
716
784
|
}
|
|
717
785
|
});
|
|
718
786
|
});
|
|
719
|
-
let
|
|
787
|
+
let data;
|
|
720
788
|
try {
|
|
721
|
-
|
|
789
|
+
data = JSON.parse(stdout);
|
|
722
790
|
} catch {
|
|
723
791
|
throw new Error("Could not parse video info from yt-dlp.");
|
|
724
792
|
}
|
|
793
|
+
if (Array.isArray(data.entries)) {
|
|
794
|
+
const mapped = data.entries.map((entry) => ({
|
|
795
|
+
url: resolveEntryUrl(entry),
|
|
796
|
+
title: entry.title,
|
|
797
|
+
duration: entry.duration
|
|
798
|
+
}));
|
|
799
|
+
const entries = mapped.filter((entry) => Boolean(entry.url));
|
|
800
|
+
if (entries.length > 1) return { kind: "playlist", title: data.title || "Playlist", entries };
|
|
801
|
+
if (entries.length === 1) {
|
|
802
|
+
if (depth >= 3) throw new Error("Could not resolve this playlist entry.");
|
|
803
|
+
return probe(ytdlp, entries[0].url, signal, depth + 1);
|
|
804
|
+
}
|
|
805
|
+
throw new Error("This playlist has no downloadable videos.");
|
|
806
|
+
}
|
|
725
807
|
const infoJsonPath = path2.join(os2.tmpdir(), `yeet-info-${process.pid}-${Date.now()}.json`);
|
|
726
808
|
await fs2.writeFile(infoJsonPath, stdout);
|
|
727
|
-
return { info, infoJsonPath };
|
|
809
|
+
return { kind: "video", info: data, infoJsonPath };
|
|
728
810
|
}
|
|
729
811
|
var MAX_VIDEO_CHOICES = 8;
|
|
730
812
|
function buildChoices(info) {
|
|
@@ -773,6 +855,25 @@ function scoreVideo(f) {
|
|
|
773
855
|
if (f.vcodec?.startsWith("avc")) score += 5e3;
|
|
774
856
|
return score;
|
|
775
857
|
}
|
|
858
|
+
var QUEUE_PRESETS = [
|
|
859
|
+
{ kind: "video", label: "best available \xB7 mp4", args: ["-f", "bv*+ba/b", "--merge-output-format", "mp4"] },
|
|
860
|
+
{
|
|
861
|
+
kind: "video",
|
|
862
|
+
label: "1080p \xB7 mp4",
|
|
863
|
+
args: ["-f", "bv*[height<=1080]+ba/b[height<=1080]/b", "--merge-output-format", "mp4"]
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
kind: "video",
|
|
867
|
+
label: "720p \xB7 mp4",
|
|
868
|
+
args: ["-f", "bv*[height<=720]+ba/b[height<=720]/b", "--merge-output-format", "mp4"]
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
kind: "video",
|
|
872
|
+
label: "480p \xB7 mp4",
|
|
873
|
+
args: ["-f", "bv*[height<=480]+ba/b[height<=480]/b", "--merge-output-format", "mp4"]
|
|
874
|
+
},
|
|
875
|
+
{ kind: "audio", label: "audio only \xB7 mp3", args: ["-f", "ba/b", "-x", "--audio-format", "mp3", "--audio-quality", "0"] }
|
|
876
|
+
];
|
|
776
877
|
var PROGRESS_PREFIX = "YEET|";
|
|
777
878
|
var PROGRESS_TEMPLATE = `${PROGRESS_PREFIX}%(progress.downloaded_bytes)s|%(progress.total_bytes)s|%(progress.total_bytes_estimate)s|%(progress.speed)s|%(progress.eta)s`;
|
|
778
879
|
var activeChild;
|
|
@@ -876,7 +977,7 @@ function cleanYtDlpError(stderr) {
|
|
|
876
977
|
}
|
|
877
978
|
|
|
878
979
|
// src/app.tsx
|
|
879
|
-
import { Fragment as Fragment2, jsx as
|
|
980
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
880
981
|
var OUT_DIR = path3.join(os3.homedir(), "Downloads");
|
|
881
982
|
var YEET_BUTTON = "yeet";
|
|
882
983
|
var DONE_LABEL = "\u21B5 yeet another";
|
|
@@ -884,13 +985,13 @@ var TAGLINE = "yeet any video. paste. yeet. done.";
|
|
|
884
985
|
var choiceLabel = (choice) => `${choice.kind === "audio" ? "\u266A " : "\u25B6 "}${choice.label}`;
|
|
885
986
|
function ChoiceIndicator({ isSelected }) {
|
|
886
987
|
const theme = useTheme();
|
|
887
|
-
return /* @__PURE__ */
|
|
988
|
+
return /* @__PURE__ */ jsx9(Box6, { marginRight: 1, children: /* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: isSelected ? "\u276F" : " " }) });
|
|
888
989
|
}
|
|
889
990
|
function ChoiceItem({ isSelected, label }) {
|
|
890
991
|
const theme = useTheme();
|
|
891
|
-
return /* @__PURE__ */
|
|
992
|
+
return /* @__PURE__ */ jsx9(Text8, { color: theme.primary, bold: isSelected, children: label });
|
|
892
993
|
}
|
|
893
|
-
var Gap = ({ lines = 1 }) => /* @__PURE__ */
|
|
994
|
+
var Gap = ({ lines = 1 }) => /* @__PURE__ */ jsx9(Box6, { flexDirection: "column", flexShrink: 0, children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx9(Text8, { children: " " }, i)) });
|
|
894
995
|
function partLabel(progress) {
|
|
895
996
|
return progress.totalParts > 1 ? `part ${progress.part + 1}/${progress.totalParts} ` : "";
|
|
896
997
|
}
|
|
@@ -904,6 +1005,48 @@ function indeterminateMeta(progress) {
|
|
|
904
1005
|
const speed = progress.speed ? formatSpeed(progress.speed) : "";
|
|
905
1006
|
return `${partLabel(progress)}${bytes.padStart(8)} ${speed.padEnd(10)}`;
|
|
906
1007
|
}
|
|
1008
|
+
function DownloadStatus({
|
|
1009
|
+
theme,
|
|
1010
|
+
progress,
|
|
1011
|
+
processing,
|
|
1012
|
+
refreshing
|
|
1013
|
+
}) {
|
|
1014
|
+
if (processing) {
|
|
1015
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1016
|
+
/* @__PURE__ */ jsx9(ProgressBar, { percent: 1 }),
|
|
1017
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1018
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1019
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }) }),
|
|
1020
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: " processing\u2026" })
|
|
1021
|
+
] })
|
|
1022
|
+
] });
|
|
1023
|
+
}
|
|
1024
|
+
if (progress?.totalBytes) {
|
|
1025
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1026
|
+
/* @__PURE__ */ jsx9(ProgressBar, { percent: progress.downloadedBytes / progress.totalBytes }),
|
|
1027
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1028
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: downloadMeta(progress) })
|
|
1029
|
+
] });
|
|
1030
|
+
}
|
|
1031
|
+
if (progress) {
|
|
1032
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1033
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1034
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }) }),
|
|
1035
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: " downloading\u2026" })
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1038
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: indeterminateMeta(progress) })
|
|
1039
|
+
] });
|
|
1040
|
+
}
|
|
1041
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1042
|
+
/* @__PURE__ */ jsx9(ProgressBar, { percent: 0 }),
|
|
1043
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1044
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1045
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }) }),
|
|
1046
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: refreshing ? " link expired \u2014 grabbing a fresh one\u2026" : " starting download\u2026" })
|
|
1047
|
+
] })
|
|
1048
|
+
] });
|
|
1049
|
+
}
|
|
907
1050
|
var HINTS = {
|
|
908
1051
|
input: [
|
|
909
1052
|
["\u21B5", "yeet"],
|
|
@@ -924,6 +1067,21 @@ var HINTS = {
|
|
|
924
1067
|
["^c", "quit"]
|
|
925
1068
|
],
|
|
926
1069
|
done: [["^c", "quit"]],
|
|
1070
|
+
"batch-picking": [
|
|
1071
|
+
["\u2191\u2193", "choose"],
|
|
1072
|
+
["\u21B5", "yeet all"],
|
|
1073
|
+
["esc", "back"],
|
|
1074
|
+
["^c", "quit"]
|
|
1075
|
+
],
|
|
1076
|
+
"batch-running": [
|
|
1077
|
+
["s", "skip"],
|
|
1078
|
+
["esc", "cancel"],
|
|
1079
|
+
["^c", "quit"]
|
|
1080
|
+
],
|
|
1081
|
+
"batch-done": [
|
|
1082
|
+
["\u21B5", "done"],
|
|
1083
|
+
["^c", "quit"]
|
|
1084
|
+
],
|
|
927
1085
|
error: [
|
|
928
1086
|
["\u21B5", "try again"],
|
|
929
1087
|
["^c", "quit"]
|
|
@@ -934,7 +1092,7 @@ function App({ initialThemeMode: initialThemeMode2 = "auto", ...props }) {
|
|
|
934
1092
|
const cycleTheme = useCallback(() => {
|
|
935
1093
|
setThemeMode(nextThemeMode);
|
|
936
1094
|
}, []);
|
|
937
|
-
return /* @__PURE__ */
|
|
1095
|
+
return /* @__PURE__ */ jsx9(ThemeProvider, { mode: themeMode, children: /* @__PURE__ */ jsx9(AppContent, { ...props, cycleTheme }) });
|
|
938
1096
|
}
|
|
939
1097
|
function AppContent({
|
|
940
1098
|
initialUrl: initialUrl2,
|
|
@@ -953,12 +1111,18 @@ function AppContent({
|
|
|
953
1111
|
const [choices, setChoices] = useState4([]);
|
|
954
1112
|
const ytdlpRef = useRef3("");
|
|
955
1113
|
const highlightRef = useRef3(0);
|
|
1114
|
+
const batchHighlightRef = useRef3(0);
|
|
956
1115
|
const infoJsonRef = useRef3(void 0);
|
|
957
1116
|
const abortRef = useRef3(void 0);
|
|
958
1117
|
const [phase, setPhase] = useState4(initialUrl2 ? { name: "probing", status: "warming up\u2026" } : { name: "input" });
|
|
1118
|
+
const [queue, setQueue] = useState4([]);
|
|
1119
|
+
const [batchLabel, setBatchLabel] = useState4("");
|
|
1120
|
+
const [batchItems, setBatchItems] = useState4([]);
|
|
1121
|
+
const batchControlRef = useRef3({ cancelled: false });
|
|
959
1122
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
960
1123
|
const boxWidth = Math.max(14, Math.min(64, columns - 6));
|
|
961
1124
|
const contentWidth = Math.max(10, Math.min(columns - 4, 78));
|
|
1125
|
+
const batchWidth = Math.max(20, Math.min(columns - 10, 60));
|
|
962
1126
|
const startProbe = useCallback(async (targetUrl) => {
|
|
963
1127
|
const controller = new AbortController();
|
|
964
1128
|
abortRef.current = controller;
|
|
@@ -969,11 +1133,17 @@ function AppContent({
|
|
|
969
1133
|
ytdlpRef.current = ytdlp;
|
|
970
1134
|
if (controller.signal.aborted) return;
|
|
971
1135
|
setPhase({ name: "probing", status: "fetching video info\u2026" });
|
|
972
|
-
const
|
|
1136
|
+
const result = await probe(ytdlp, targetUrl, controller.signal);
|
|
973
1137
|
if (controller.signal.aborted) return;
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1138
|
+
if (result.kind === "playlist") {
|
|
1139
|
+
setBatchLabel(result.title);
|
|
1140
|
+
setBatchItems(makeBatchItems(result.entries));
|
|
1141
|
+
setPhase({ name: "batch-picking" });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
infoJsonRef.current = result.infoJsonPath;
|
|
1145
|
+
setInfo(result.info);
|
|
1146
|
+
setChoices(buildChoices(result.info));
|
|
977
1147
|
highlightRef.current = 0;
|
|
978
1148
|
setPhase({ name: "picking" });
|
|
979
1149
|
} catch (error) {
|
|
@@ -990,6 +1160,8 @@ function AppContent({
|
|
|
990
1160
|
setPlatform(void 0);
|
|
991
1161
|
setInfo(void 0);
|
|
992
1162
|
setChoices([]);
|
|
1163
|
+
setBatchLabel("");
|
|
1164
|
+
setBatchItems([]);
|
|
993
1165
|
setPhase({ name: "input" });
|
|
994
1166
|
}, []);
|
|
995
1167
|
const cancelRun = useCallback(() => {
|
|
@@ -997,20 +1169,120 @@ function AppContent({
|
|
|
997
1169
|
resetToInput();
|
|
998
1170
|
setUrlInput(url);
|
|
999
1171
|
}, [resetToInput, url]);
|
|
1172
|
+
const startBatch = useCallback((initialItems, preset) => {
|
|
1173
|
+
batchControlRef.current = { cancelled: false };
|
|
1174
|
+
let items = initialItems.map((item) => ({ ...item }));
|
|
1175
|
+
setBatchItems(items);
|
|
1176
|
+
setPhase({ name: "batch-running", preset });
|
|
1177
|
+
void (async () => {
|
|
1178
|
+
let ytdlp;
|
|
1179
|
+
try {
|
|
1180
|
+
ytdlp = ytdlpRef.current || await ensureYtDlp(() => {
|
|
1181
|
+
});
|
|
1182
|
+
ytdlpRef.current = ytdlp;
|
|
1183
|
+
} catch (error) {
|
|
1184
|
+
items = items.map((item) => ({ ...item, status: "error", error: error instanceof Error ? error.message : String(error) }));
|
|
1185
|
+
setBatchItems(items);
|
|
1186
|
+
setPhase({ name: "batch-done" });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
const ffmpegLocation = await findFfmpeg();
|
|
1190
|
+
for (let index = 0; index < items.length; index++) {
|
|
1191
|
+
if (batchControlRef.current.cancelled) {
|
|
1192
|
+
items = items.map((item, i) => i >= index && item.status === "pending" ? { ...item, status: "skipped" } : item);
|
|
1193
|
+
setBatchItems(items);
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
const controller = new AbortController();
|
|
1197
|
+
batchControlRef.current.controller = controller;
|
|
1198
|
+
items = items.map((item, i) => i === index ? { ...item, status: "active" } : item);
|
|
1199
|
+
setBatchItems(items);
|
|
1200
|
+
try {
|
|
1201
|
+
const handlers = {
|
|
1202
|
+
onProgress: (progress) => {
|
|
1203
|
+
items = items.map((item, i) => i === index ? { ...item, progress, processing: false } : item);
|
|
1204
|
+
setBatchItems(items);
|
|
1205
|
+
},
|
|
1206
|
+
onProcessing: () => {
|
|
1207
|
+
items = items.map((item, i) => i === index ? { ...item, processing: true } : item);
|
|
1208
|
+
setBatchItems(items);
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
const filepath = await download(
|
|
1212
|
+
{ ytdlp, ffmpegLocation, url: items[index].url, choice: preset, outDir: OUT_DIR },
|
|
1213
|
+
handlers,
|
|
1214
|
+
controller.signal
|
|
1215
|
+
);
|
|
1216
|
+
items = items.map((item, i) => i === index ? { ...item, status: "done", filepath } : item);
|
|
1217
|
+
setBatchItems(items);
|
|
1218
|
+
setHistory(addToHistory(items[index].url));
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
if (controller.signal.aborted) {
|
|
1221
|
+
items = items.map((item, i) => i === index ? { ...item, status: "skipped" } : item);
|
|
1222
|
+
} else {
|
|
1223
|
+
items = items.map(
|
|
1224
|
+
(item, i) => i === index ? { ...item, status: "error", error: error instanceof Error ? error.message : String(error) } : item
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
setBatchItems(items);
|
|
1228
|
+
if (batchControlRef.current.cancelled) {
|
|
1229
|
+
items = items.map((item, i) => i > index ? { ...item, status: "skipped" } : item);
|
|
1230
|
+
setBatchItems(items);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
onOutcome({ count: items.filter((item) => item.status === "done").length });
|
|
1236
|
+
setPhase({ name: "batch-done" });
|
|
1237
|
+
})();
|
|
1238
|
+
}, [onOutcome]);
|
|
1000
1239
|
useInput2(
|
|
1001
1240
|
(input, key) => {
|
|
1002
1241
|
if (key.ctrl && input === "t") {
|
|
1003
1242
|
cycleTheme();
|
|
1004
1243
|
return;
|
|
1005
1244
|
}
|
|
1006
|
-
if (key.
|
|
1007
|
-
|
|
1008
|
-
|
|
1245
|
+
if (key.ctrl && input === "q" && phase.name === "input") {
|
|
1246
|
+
const trimmed = urlInput.trim();
|
|
1247
|
+
if (isProbablyUrl(trimmed)) {
|
|
1248
|
+
setQueue((q) => [...q, trimmed]);
|
|
1249
|
+
setUrlInput("");
|
|
1250
|
+
}
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (input === "s" && phase.name === "batch-running") {
|
|
1254
|
+
batchControlRef.current.controller?.abort();
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
if (key.escape && phase.name === "batch-running") {
|
|
1258
|
+
batchControlRef.current.cancelled = true;
|
|
1259
|
+
batchControlRef.current.controller?.abort();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (key.escape && (phase.name === "picking" || phase.name === "batch-picking" || phase.name === "error" || phase.name === "done" || phase.name === "batch-done")) {
|
|
1263
|
+
resetToInput();
|
|
1264
|
+
}
|
|
1265
|
+
if (key.escape && phase.name === "probing") cancelRun();
|
|
1266
|
+
if (key.return && (phase.name === "error" || phase.name === "done" || phase.name === "batch-done")) resetToInput();
|
|
1009
1267
|
},
|
|
1010
1268
|
{ isActive: Boolean(process.stdin.isTTY) }
|
|
1011
1269
|
);
|
|
1012
1270
|
const handleUrlSubmit = (value) => {
|
|
1013
1271
|
const trimmed = value.trim();
|
|
1272
|
+
if (queue.length > 0) {
|
|
1273
|
+
if (trimmed && !isProbablyUrl(trimmed)) {
|
|
1274
|
+
setPhase({ name: "input", warning: "that doesn\u2019t look like a link \u2014 clear it or paste a full url" });
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const urls = trimmed ? [...queue, trimmed] : [...queue];
|
|
1278
|
+
setQueue([]);
|
|
1279
|
+
setUrlInput("");
|
|
1280
|
+
const items = makeBatchItems(urls.map((u) => ({ url: u })));
|
|
1281
|
+
setBatchLabel(`${items.length} queued link${items.length === 1 ? "" : "s"}`);
|
|
1282
|
+
setBatchItems(items);
|
|
1283
|
+
setPhase({ name: "batch-picking" });
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1014
1286
|
if (!isProbablyUrl(trimmed)) {
|
|
1015
1287
|
setPhase({ name: "input", warning: "that doesn\u2019t look like a link \u2014 paste a full url" });
|
|
1016
1288
|
return;
|
|
@@ -1018,7 +1290,7 @@ function AppContent({
|
|
|
1018
1290
|
setUrl(trimmed);
|
|
1019
1291
|
void startProbe(trimmed);
|
|
1020
1292
|
};
|
|
1021
|
-
const clipboardOffered = Boolean(clipboardUrl2) && urlInput === "";
|
|
1293
|
+
const clipboardOffered = Boolean(clipboardUrl2) && urlInput === "" && queue.length === 0;
|
|
1022
1294
|
const clipboardAccepted = Boolean(clipboardUrl2) && urlInput === clipboardUrl2;
|
|
1023
1295
|
const handlePick = (item) => {
|
|
1024
1296
|
const choice = choices[item.value];
|
|
@@ -1052,18 +1324,39 @@ function AppContent({
|
|
|
1052
1324
|
}
|
|
1053
1325
|
})();
|
|
1054
1326
|
};
|
|
1327
|
+
const handleBatchPick = (item) => {
|
|
1328
|
+
const preset = QUEUE_PRESETS[item.value];
|
|
1329
|
+
startBatch(batchItems, preset);
|
|
1330
|
+
};
|
|
1055
1331
|
let hints = [...HINTS[phase.name], ["^t", `theme:${theme.mode}`]];
|
|
1056
|
-
if (phase.name === "input"
|
|
1057
|
-
|
|
1332
|
+
if (phase.name === "input") {
|
|
1333
|
+
if (queue.length > 0) {
|
|
1334
|
+
hints = [hints[0], ["^q", `queued (${queue.length})`], ...hints.slice(1)];
|
|
1335
|
+
} else if (isProbablyUrl(urlInput.trim())) {
|
|
1336
|
+
hints = [hints[0], ["^q", "add to queue"], ...hints.slice(1)];
|
|
1337
|
+
} else if (history.length > 0) {
|
|
1338
|
+
hints = [hints[0], ["\u2191", "history"], ...hints.slice(1)];
|
|
1339
|
+
}
|
|
1058
1340
|
}
|
|
1059
1341
|
const hintAction = (key) => {
|
|
1060
1342
|
if (key === "^c") return () => exit();
|
|
1061
1343
|
if (key === "^t") return cycleTheme;
|
|
1062
|
-
if (key === "esc")
|
|
1344
|
+
if (key === "esc") {
|
|
1345
|
+
if (phase.name === "probing") return cancelRun;
|
|
1346
|
+
if (phase.name === "batch-running") {
|
|
1347
|
+
return () => {
|
|
1348
|
+
batchControlRef.current.cancelled = true;
|
|
1349
|
+
batchControlRef.current.controller?.abort();
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
return resetToInput;
|
|
1353
|
+
}
|
|
1354
|
+
if (key === "s" && phase.name === "batch-running") return () => batchControlRef.current.controller?.abort();
|
|
1063
1355
|
if (key === "\u21B5") {
|
|
1064
1356
|
if (phase.name === "input") return () => handleUrlSubmit(urlInput);
|
|
1065
1357
|
if (phase.name === "picking") return () => handlePick({ value: highlightRef.current });
|
|
1066
|
-
if (phase.name === "
|
|
1358
|
+
if (phase.name === "batch-picking") return () => handleBatchPick({ value: batchHighlightRef.current });
|
|
1359
|
+
if (phase.name === "error" || phase.name === "done" || phase.name === "batch-done") return resetToInput;
|
|
1067
1360
|
}
|
|
1068
1361
|
return void 0;
|
|
1069
1362
|
};
|
|
@@ -1076,7 +1369,12 @@ function AppContent({
|
|
|
1076
1369
|
clickTargets.push({ match: choiceLabel(choice), action: () => handlePick({ value: index }) });
|
|
1077
1370
|
}
|
|
1078
1371
|
}
|
|
1079
|
-
if (phase.name === "
|
|
1372
|
+
if (phase.name === "batch-picking") {
|
|
1373
|
+
for (const [index, choice] of QUEUE_PRESETS.entries()) {
|
|
1374
|
+
clickTargets.push({ match: choiceLabel(choice), action: () => handleBatchPick({ value: index }) });
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (phase.name === "done" || phase.name === "batch-done") {
|
|
1080
1378
|
clickTargets.push({ match: DONE_LABEL, padX: 4, padY: 1, action: resetToInput });
|
|
1081
1379
|
}
|
|
1082
1380
|
for (const [key, label] of hints) {
|
|
@@ -1089,8 +1387,11 @@ function AppContent({
|
|
|
1089
1387
|
if (taglineRow > 3 && y - 1 >= taglineRow - 4 && y - 1 <= taglineRow - 2) {
|
|
1090
1388
|
const span = frameRowSpan(y - 1);
|
|
1091
1389
|
if (span && x >= span[0] - 1 && x <= span[1] + 1) {
|
|
1092
|
-
if (phase.name === "probing"
|
|
1093
|
-
else if (phase.name
|
|
1390
|
+
if (phase.name === "probing") cancelRun();
|
|
1391
|
+
else if (phase.name === "batch-running") {
|
|
1392
|
+
batchControlRef.current.cancelled = true;
|
|
1393
|
+
batchControlRef.current.controller?.abort();
|
|
1394
|
+
} else if (phase.name !== "input") resetToInput();
|
|
1094
1395
|
return;
|
|
1095
1396
|
}
|
|
1096
1397
|
}
|
|
@@ -1098,14 +1399,16 @@ function AppContent({
|
|
|
1098
1399
|
},
|
|
1099
1400
|
Boolean(process.stdin.isTTY)
|
|
1100
1401
|
);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
/* @__PURE__ */
|
|
1105
|
-
/* @__PURE__ */
|
|
1106
|
-
/* @__PURE__ */
|
|
1107
|
-
|
|
1108
|
-
|
|
1402
|
+
const batchActive = batchItems.find((item) => item.status === "active");
|
|
1403
|
+
const batchSummaryCounts = batchSummary(batchItems);
|
|
1404
|
+
return /* @__PURE__ */ jsxs7(FullScreen, { children: [
|
|
1405
|
+
/* @__PURE__ */ jsx9(Logo, {}),
|
|
1406
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1407
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: TAGLINE }),
|
|
1408
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: "youtube \xB7 x \xB7 instagram \xB7 threads \xB7 tiktok \xB7 +1800 more" }),
|
|
1409
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1410
|
+
phase.name === "input" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1411
|
+
/* @__PURE__ */ jsx9(FramedInput, { title: "Paste a link", width: boxWidth, button: YEET_BUTTON, children: /* @__PURE__ */ jsx9(
|
|
1109
1412
|
TextInput,
|
|
1110
1413
|
{
|
|
1111
1414
|
value: urlInput,
|
|
@@ -1120,24 +1423,29 @@ function AppContent({
|
|
|
1120
1423
|
}
|
|
1121
1424
|
}
|
|
1122
1425
|
) }),
|
|
1123
|
-
phase.warning ? /* @__PURE__ */
|
|
1426
|
+
phase.warning ? /* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1124
1427
|
"\u2717 ",
|
|
1125
1428
|
phase.warning
|
|
1126
|
-
] }) :
|
|
1429
|
+
] }) : queue.length > 0 ? /* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1430
|
+
queue.length,
|
|
1431
|
+
" link",
|
|
1432
|
+
queue.length === 1 ? "" : "s",
|
|
1433
|
+
" queued \u2014 \u21B5 to start, ^q to add another"
|
|
1434
|
+
] }) : clipboardOffered ? /* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: "link in your clipboard \u2014 \u21E5 to paste it" }) : clipboardAccepted ? /* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: "from your clipboard \u2014 \u21B5 to yeet it" }) : null
|
|
1127
1435
|
] }),
|
|
1128
|
-
phase.name === "probing" && /* @__PURE__ */
|
|
1129
|
-
phase.name === "picking" && platform && /* @__PURE__ */
|
|
1130
|
-
/* @__PURE__ */
|
|
1131
|
-
wrapText(info?.title ?? "", Math.max(10, contentWidth - 41)).map((line, index) => /* @__PURE__ */
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
/* @__PURE__ */
|
|
1436
|
+
phase.name === "probing" && /* @__PURE__ */ jsx9(Box6, { flexDirection: "column", alignItems: "center", children: /* @__PURE__ */ jsx9(FramedInput, { title: platform ? platform.label : "Paste a link", width: boxWidth, button: YEET_BUTTON, buttonDim: true, children: /* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: url.length > boxWidth - 8 ? `${url.slice(0, boxWidth - 9)}\u2026` : url }) }) }),
|
|
1437
|
+
phase.name === "picking" && platform && /* @__PURE__ */ jsxs7(Box6, { width: contentWidth, children: [
|
|
1438
|
+
/* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", flexGrow: 1, flexBasis: 0, paddingTop: 1, paddingRight: 3, children: [
|
|
1439
|
+
wrapText(info?.title ?? "", Math.max(10, contentWidth - 41)).map((line, index) => /* @__PURE__ */ jsx9(Text8, { bold: true, color: theme.primary, children: line }, index)),
|
|
1440
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1441
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1134
1442
|
"\u25B8 ",
|
|
1135
1443
|
platform.label,
|
|
1136
1444
|
info?.duration ? ` \xB7 ${formatDuration(info.duration)}` : "",
|
|
1137
1445
|
info?.uploader ? ` \xB7 ${info.uploader}` : ""
|
|
1138
1446
|
] })
|
|
1139
1447
|
] }),
|
|
1140
|
-
/* @__PURE__ */
|
|
1448
|
+
/* @__PURE__ */ jsx9(Panel, { title: "Download", width: 38, children: /* @__PURE__ */ jsx9(
|
|
1141
1449
|
SelectInput,
|
|
1142
1450
|
{
|
|
1143
1451
|
indicatorComponent: ChoiceIndicator,
|
|
@@ -1152,71 +1460,130 @@ function AppContent({
|
|
|
1152
1460
|
}
|
|
1153
1461
|
) })
|
|
1154
1462
|
] }),
|
|
1155
|
-
phase.name === "
|
|
1156
|
-
/* @__PURE__ */
|
|
1463
|
+
phase.name === "batch-picking" && /* @__PURE__ */ jsxs7(Box6, { width: contentWidth, children: [
|
|
1464
|
+
/* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", flexGrow: 1, flexBasis: 0, paddingTop: 1, paddingRight: 3, children: [
|
|
1465
|
+
wrapText(batchLabel, Math.max(10, contentWidth - 41)).map((line, index) => /* @__PURE__ */ jsx9(Text8, { bold: true, color: theme.primary, children: line }, index)),
|
|
1466
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1467
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1468
|
+
"\u25B8 ",
|
|
1469
|
+
batchItems.length,
|
|
1470
|
+
" video",
|
|
1471
|
+
batchItems.length === 1 ? "" : "s",
|
|
1472
|
+
" \xB7 same format for all"
|
|
1473
|
+
] }),
|
|
1474
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1475
|
+
/* @__PURE__ */ jsx9(QueueList, { items: batchItems, width: Math.max(10, contentWidth - 41) })
|
|
1476
|
+
] }),
|
|
1477
|
+
/* @__PURE__ */ jsx9(Panel, { title: "Download", width: 38, children: /* @__PURE__ */ jsx9(
|
|
1478
|
+
SelectInput,
|
|
1479
|
+
{
|
|
1480
|
+
indicatorComponent: ChoiceIndicator,
|
|
1481
|
+
itemComponent: ChoiceItem,
|
|
1482
|
+
items: QUEUE_PRESETS.map((choice, index) => ({
|
|
1483
|
+
key: String(index),
|
|
1484
|
+
label: choiceLabel(choice),
|
|
1485
|
+
value: index
|
|
1486
|
+
})),
|
|
1487
|
+
onSelect: handleBatchPick,
|
|
1488
|
+
onHighlight: (item) => batchHighlightRef.current = item.value
|
|
1489
|
+
}
|
|
1490
|
+
) })
|
|
1491
|
+
] }),
|
|
1492
|
+
phase.name === "downloading" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1493
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1157
1494
|
info?.title ? `${truncate(info.title, 42)} \xB7 ` : "",
|
|
1158
1495
|
phase.choice.label
|
|
1159
1496
|
] }),
|
|
1160
|
-
/* @__PURE__ */
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
/* @__PURE__ */ jsx8(Gap, {}),
|
|
1178
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: indeterminateMeta(phase.progress) })
|
|
1179
|
-
] }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1180
|
-
/* @__PURE__ */ jsx8(ProgressBar, { percent: 0 }),
|
|
1181
|
-
/* @__PURE__ */ jsx8(Gap, {}),
|
|
1182
|
-
/* @__PURE__ */ jsxs6(Text7, { children: [
|
|
1183
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.primary, children: /* @__PURE__ */ jsx8(Spinner, { type: "dots" }) }),
|
|
1184
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: phase.refreshing ? " link expired \u2014 grabbing a fresh one\u2026" : " starting download\u2026" })
|
|
1185
|
-
] })
|
|
1497
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1498
|
+
/* @__PURE__ */ jsx9(DownloadStatus, { theme, progress: phase.progress, processing: phase.processing, refreshing: phase.refreshing })
|
|
1499
|
+
] }),
|
|
1500
|
+
phase.name === "batch-running" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1501
|
+
/* @__PURE__ */ jsx9(QueueList, { items: batchItems, width: batchWidth }),
|
|
1502
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1503
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: batchActive ? truncate(batchActive.title || batchActive.url, 46) : "" }),
|
|
1504
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1505
|
+
/* @__PURE__ */ jsx9(DownloadStatus, { theme, progress: batchActive?.progress, processing: batchActive?.processing }),
|
|
1506
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1507
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1508
|
+
batchSummaryCounts.done,
|
|
1509
|
+
"/",
|
|
1510
|
+
batchItems.length,
|
|
1511
|
+
" done",
|
|
1512
|
+
batchSummaryCounts.error ? ` \xB7 ${batchSummaryCounts.error} failed` : "",
|
|
1513
|
+
batchSummaryCounts.skipped ? ` \xB7 ${batchSummaryCounts.skipped} skipped` : ""
|
|
1186
1514
|
] })
|
|
1187
1515
|
] }),
|
|
1188
|
-
phase.name === "done" && /* @__PURE__ */
|
|
1189
|
-
/* @__PURE__ */
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
/* @__PURE__ */
|
|
1516
|
+
phase.name === "done" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1517
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1518
|
+
/* @__PURE__ */ jsx9(Text8, { bold: true, color: theme.primary, children: "\u2713 yeeted! " }),
|
|
1519
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: "find your file in:" })
|
|
1520
|
+
] }),
|
|
1521
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: shortenPath(phase.filepath, os3.homedir(), 60) }),
|
|
1522
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1523
|
+
/* @__PURE__ */ jsx9(
|
|
1524
|
+
Box6,
|
|
1525
|
+
{
|
|
1526
|
+
borderStyle: "round",
|
|
1527
|
+
borderColor: theme.gray,
|
|
1528
|
+
borderDimColor: theme.dimSecondary,
|
|
1529
|
+
borderBackgroundColor: theme.background,
|
|
1530
|
+
paddingX: 3,
|
|
1531
|
+
children: /* @__PURE__ */ jsx9(Text8, { bold: true, color: theme.primary, children: DONE_LABEL })
|
|
1532
|
+
}
|
|
1533
|
+
)
|
|
1534
|
+
] }),
|
|
1535
|
+
phase.name === "batch-done" && /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", alignItems: "center", children: [
|
|
1536
|
+
/* @__PURE__ */ jsx9(QueueList, { items: batchItems, width: batchWidth }),
|
|
1537
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1538
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1539
|
+
/* @__PURE__ */ jsxs7(Text8, { bold: true, color: theme.primary, children: [
|
|
1540
|
+
"\u2713 ",
|
|
1541
|
+
batchSummaryCounts.done,
|
|
1542
|
+
"/",
|
|
1543
|
+
batchItems.length,
|
|
1544
|
+
" yeeted"
|
|
1545
|
+
] }),
|
|
1546
|
+
batchSummaryCounts.error ? /* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1547
|
+
" \xB7 ",
|
|
1548
|
+
batchSummaryCounts.error,
|
|
1549
|
+
" failed"
|
|
1550
|
+
] }) : null,
|
|
1551
|
+
batchSummaryCounts.skipped ? /* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1552
|
+
" \xB7 ",
|
|
1553
|
+
batchSummaryCounts.skipped,
|
|
1554
|
+
" skipped"
|
|
1555
|
+
] }) : null
|
|
1556
|
+
] }),
|
|
1557
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1558
|
+
"saved to ",
|
|
1559
|
+
shortenPath(OUT_DIR, os3.homedir(), 60)
|
|
1192
1560
|
] }),
|
|
1193
|
-
/* @__PURE__ */
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
|
|
1196
|
-
Box5,
|
|
1561
|
+
/* @__PURE__ */ jsx9(Gap, {}),
|
|
1562
|
+
/* @__PURE__ */ jsx9(
|
|
1563
|
+
Box6,
|
|
1197
1564
|
{
|
|
1198
1565
|
borderStyle: "round",
|
|
1199
1566
|
borderColor: theme.gray,
|
|
1200
1567
|
borderDimColor: theme.dimSecondary,
|
|
1201
1568
|
borderBackgroundColor: theme.background,
|
|
1202
1569
|
paddingX: 3,
|
|
1203
|
-
children: /* @__PURE__ */
|
|
1570
|
+
children: /* @__PURE__ */ jsx9(Text8, { bold: true, color: theme.primary, children: DONE_LABEL })
|
|
1204
1571
|
}
|
|
1205
1572
|
)
|
|
1206
1573
|
] }),
|
|
1207
|
-
phase.name === "error" && /* @__PURE__ */
|
|
1574
|
+
phase.name === "error" && /* @__PURE__ */ jsx9(Box6, { flexDirection: "column", alignItems: "center", width: Math.max(10, Math.min(columns - 6, 72)), children: /* @__PURE__ */ jsxs7(Text8, { bold: true, color: theme.primary, children: [
|
|
1208
1575
|
"\u2717 ",
|
|
1209
1576
|
phase.message
|
|
1210
1577
|
] }) }),
|
|
1211
|
-
hints.length > 0 ? /* @__PURE__ */
|
|
1212
|
-
/* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1578
|
+
hints.length > 0 ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1579
|
+
/* @__PURE__ */ jsx9(Gap, { lines: 2 }),
|
|
1580
|
+
/* @__PURE__ */ jsx9(
|
|
1214
1581
|
Shortcuts,
|
|
1215
1582
|
{
|
|
1216
1583
|
items: hints,
|
|
1217
|
-
leading: phase.name === "probing" ? /* @__PURE__ */
|
|
1218
|
-
/* @__PURE__ */
|
|
1219
|
-
/* @__PURE__ */
|
|
1584
|
+
leading: phase.name === "probing" ? /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
1585
|
+
/* @__PURE__ */ jsx9(Text8, { color: theme.primary, children: /* @__PURE__ */ jsx9(Spinner2, { type: "dots" }) }),
|
|
1586
|
+
/* @__PURE__ */ jsxs7(Text8, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
1220
1587
|
" ",
|
|
1221
1588
|
phase.status
|
|
1222
1589
|
] })
|
|
@@ -1275,7 +1642,7 @@ function readClipboard() {
|
|
|
1275
1642
|
}
|
|
1276
1643
|
|
|
1277
1644
|
// src/cli.tsx
|
|
1278
|
-
import { jsx as
|
|
1645
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1279
1646
|
var VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
1280
1647
|
var HELP = `
|
|
1281
1648
|
yeet \u2014 yeet any video. paste. yeet. done.
|
|
@@ -1333,7 +1700,7 @@ if (isTTY) {
|
|
|
1333
1700
|
}
|
|
1334
1701
|
var outcome = {};
|
|
1335
1702
|
var { waitUntilExit } = render(
|
|
1336
|
-
/* @__PURE__ */
|
|
1703
|
+
/* @__PURE__ */ jsx10(
|
|
1337
1704
|
App,
|
|
1338
1705
|
{
|
|
1339
1706
|
initialUrl,
|