@postfetch/core 0.12.2 → 0.12.4
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/hls.d.ts +3 -1
- package/dist/index.js +20 -7
- package/package.json +1 -1
- package/src/hls.ts +7 -4
- package/src/twitter.ts +1 -1
- package/src/youtube.ts +15 -1
package/dist/hls.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ export type HlsMaster = {
|
|
|
12
12
|
audio: Record<string, string>;
|
|
13
13
|
};
|
|
14
14
|
export declare function isMasterPlaylist(text: string): boolean;
|
|
15
|
-
export declare function parseMaster(text: string, baseUrl: string
|
|
15
|
+
export declare function parseMaster(text: string, baseUrl: string, options?: {
|
|
16
|
+
preferOriginalAudio?: boolean;
|
|
17
|
+
}): HlsMaster;
|
|
16
18
|
export declare function assembleHls(net: Net, playlistUrl: string, headers: HeadersInit): Promise<Uint8Array>;
|
package/dist/index.js
CHANGED
|
@@ -1218,11 +1218,11 @@ function mediaFragment(fragment, sequence) {
|
|
|
1218
1218
|
function isMasterPlaylist(text) {
|
|
1219
1219
|
return text.includes("#EXT-X-STREAM-INF");
|
|
1220
1220
|
}
|
|
1221
|
-
function parseMaster(text, baseUrl) {
|
|
1221
|
+
function parseMaster(text, baseUrl, options = {}) {
|
|
1222
1222
|
const lines = text.split(`
|
|
1223
1223
|
`).map((line) => line.trim());
|
|
1224
1224
|
const audio = {};
|
|
1225
|
-
const
|
|
1225
|
+
const audioPreference = {};
|
|
1226
1226
|
const variants = [];
|
|
1227
1227
|
for (let index = 0;index < lines.length; index += 1) {
|
|
1228
1228
|
const line = lines[index];
|
|
@@ -1230,10 +1230,12 @@ function parseMaster(text, baseUrl) {
|
|
|
1230
1230
|
const group = line.match(/GROUP-ID="([^"]+)"/)?.[1];
|
|
1231
1231
|
const uri = line.match(/URI="([^"]+)"/)?.[1];
|
|
1232
1232
|
const isDefault = /(?:^|,)DEFAULT=YES(?:,|$)/.test(line);
|
|
1233
|
-
|
|
1233
|
+
const name = line.match(/(?:^|,)NAME="([^"]+)"/)?.[1] ?? "";
|
|
1234
|
+
const isOriginal = options.preferOriginalAudio && /\boriginal\b/i.test(name);
|
|
1235
|
+
const preference = isOriginal ? 2 : isDefault ? 1 : 0;
|
|
1236
|
+
if (group && uri && (!audio[group] || preference > audioPreference[group])) {
|
|
1234
1237
|
audio[group] = new URL(uri, baseUrl).href;
|
|
1235
|
-
|
|
1236
|
-
defaultAudio.add(group);
|
|
1238
|
+
audioPreference[group] = preference;
|
|
1237
1239
|
}
|
|
1238
1240
|
} else if (line.startsWith("#EXT-X-STREAM-INF:")) {
|
|
1239
1241
|
const target = lines[index + 1];
|
|
@@ -2359,7 +2361,7 @@ function cleanTwitterText(text, tweet) {
|
|
|
2359
2361
|
break;
|
|
2360
2362
|
text = text.slice(0, trailing.index).trimEnd();
|
|
2361
2363
|
}
|
|
2362
|
-
return text.replace(/&(?:gt|lt);/g, (entity) => entity === ">" ? ">" : "<");
|
|
2364
|
+
return text.replace(/&(?:amp|gt|lt);/g, (entity) => entity === "&" ? "&" : entity === ">" ? ">" : "<");
|
|
2363
2365
|
}
|
|
2364
2366
|
function needsFullText(tweet) {
|
|
2365
2367
|
if (noteText(tweet) || string(tweet.full_text)) {
|
|
@@ -2711,7 +2713,7 @@ async function resolveVisionOs(input, id, visitorData) {
|
|
|
2711
2713
|
if (!response.ok) {
|
|
2712
2714
|
throw new Error(`YouTube HLS manifest failed: ${response.status}`);
|
|
2713
2715
|
}
|
|
2714
|
-
const master = parseMaster(await response.text(), manifestUrl);
|
|
2716
|
+
const master = parseMaster(await response.text(), manifestUrl, { preferOriginalAudio: true });
|
|
2715
2717
|
const variants = master.variants.filter((variant2) => /(?:^|,)(?:vp09|av01)\./i.test(variant2.codecs ?? "") && /(?:^|,)mp4a\.40\.2(?:,|$)/i.test(variant2.codecs ?? "") && variant2.audioGroup && master.audio[variant2.audioGroup]).sort((left, right) => Math.abs(left.width - input.preferredWidth) - Math.abs(right.width - input.preferredWidth) || right.bandwidth - left.bandwidth);
|
|
2716
2718
|
const variant = variants[0];
|
|
2717
2719
|
if (!variant?.audioGroup) {
|
|
@@ -2780,10 +2782,21 @@ function bestAudio(formats) {
|
|
|
2780
2782
|
if (!current) {
|
|
2781
2783
|
return format;
|
|
2782
2784
|
}
|
|
2785
|
+
const preference = audioPreference(format) - audioPreference(current);
|
|
2786
|
+
if (preference !== 0) {
|
|
2787
|
+
return preference > 0 ? format : current;
|
|
2788
|
+
}
|
|
2783
2789
|
return (number(format.bitrate) ?? 0) > (number(current.bitrate) ?? 0) ? format : current;
|
|
2784
2790
|
}, null);
|
|
2785
2791
|
return best ? string(best.url) : null;
|
|
2786
2792
|
}
|
|
2793
|
+
function audioPreference(format) {
|
|
2794
|
+
const track = object(format.audioTrack) ? format.audioTrack : null;
|
|
2795
|
+
if (track && /\boriginal\b/i.test(string(track.displayName) ?? "")) {
|
|
2796
|
+
return 2;
|
|
2797
|
+
}
|
|
2798
|
+
return track?.audioIsDefault === true ? 1 : 0;
|
|
2799
|
+
}
|
|
2787
2800
|
function playerBody(id, session) {
|
|
2788
2801
|
return {
|
|
2789
2802
|
contentCheckOk: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postfetch/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Zero-dependency typed core that turns Facebook, Instagram, LinkedIn, Pinterest, Reddit, SoundCloud, TikTok, X and YouTube post URLs into media files.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/hls.ts
CHANGED
|
@@ -16,10 +16,10 @@ export function isMasterPlaylist(text: string): boolean {
|
|
|
16
16
|
return text.includes("#EXT-X-STREAM-INF");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function parseMaster(text: string, baseUrl: string): HlsMaster {
|
|
19
|
+
export function parseMaster(text: string, baseUrl: string, options: { preferOriginalAudio?: boolean } = {}): HlsMaster {
|
|
20
20
|
const lines = text.split("\n").map((line) => line.trim());
|
|
21
21
|
const audio: Record<string, string> = {};
|
|
22
|
-
const
|
|
22
|
+
const audioPreference: Record<string, number> = {};
|
|
23
23
|
const variants: HlsVariant[] = [];
|
|
24
24
|
for (let index = 0; index < lines.length; index += 1) {
|
|
25
25
|
const line = lines[index];
|
|
@@ -27,9 +27,12 @@ export function parseMaster(text: string, baseUrl: string): HlsMaster {
|
|
|
27
27
|
const group = line.match(/GROUP-ID="([^"]+)"/)?.[1];
|
|
28
28
|
const uri = line.match(/URI="([^"]+)"/)?.[1];
|
|
29
29
|
const isDefault = /(?:^|,)DEFAULT=YES(?:,|$)/.test(line);
|
|
30
|
-
|
|
30
|
+
const name = line.match(/(?:^|,)NAME="([^"]+)"/)?.[1] ?? "";
|
|
31
|
+
const isOriginal = options.preferOriginalAudio && /\boriginal\b/i.test(name);
|
|
32
|
+
const preference = isOriginal ? 2 : isDefault ? 1 : 0;
|
|
33
|
+
if (group && uri && (!audio[group] || preference > audioPreference[group])) {
|
|
31
34
|
audio[group] = new URL(uri, baseUrl).href;
|
|
32
|
-
|
|
35
|
+
audioPreference[group] = preference;
|
|
33
36
|
}
|
|
34
37
|
} else if (line.startsWith("#EXT-X-STREAM-INF:")) {
|
|
35
38
|
const target = lines[index + 1];
|
package/src/twitter.ts
CHANGED
|
@@ -111,7 +111,7 @@ function cleanTwitterText(text: string | null, tweet: Json): string | null {
|
|
|
111
111
|
if (!trailing || !urls.has(trailing[1])) break;
|
|
112
112
|
text = text.slice(0, trailing.index).trimEnd();
|
|
113
113
|
}
|
|
114
|
-
return text.replace(/&(?:gt|lt);/g, (entity) => entity === ">" ? ">" : "<");
|
|
114
|
+
return text.replace(/&(?:amp|gt|lt);/g, (entity) => entity === "&" ? "&" : entity === ">" ? ">" : "<");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function needsFullText(tweet: Json): boolean {
|
package/src/youtube.ts
CHANGED
|
@@ -139,7 +139,7 @@ async function resolveVisionOs(input: ResolveContext, id: string, visitorData?:
|
|
|
139
139
|
if (!response.ok) {
|
|
140
140
|
throw new Error(`YouTube HLS manifest failed: ${response.status}`);
|
|
141
141
|
}
|
|
142
|
-
const master = parseMaster(await response.text(), manifestUrl);
|
|
142
|
+
const master = parseMaster(await response.text(), manifestUrl, { preferOriginalAudio: true });
|
|
143
143
|
// YouTube's AVC HLS variants use MPEG-TS. VP9/AV1 variants use fMP4 and
|
|
144
144
|
// work with the bundled box-level merger. Require the paired AAC-LC track.
|
|
145
145
|
const variants = master.variants.filter((variant) =>
|
|
@@ -230,11 +230,25 @@ function bestAudio(formats: Json[]): string | null {
|
|
|
230
230
|
if (!current) {
|
|
231
231
|
return format;
|
|
232
232
|
}
|
|
233
|
+
// YouTube's default can be a dub, and bitrate varies between languages.
|
|
234
|
+
// Select the original track before comparing audio quality.
|
|
235
|
+
const preference = audioPreference(format) - audioPreference(current);
|
|
236
|
+
if (preference !== 0) {
|
|
237
|
+
return preference > 0 ? format : current;
|
|
238
|
+
}
|
|
233
239
|
return (number(format.bitrate) ?? 0) > (number(current.bitrate) ?? 0) ? format : current;
|
|
234
240
|
}, null);
|
|
235
241
|
return best ? string(best.url) : null;
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
function audioPreference(format: Json): number {
|
|
245
|
+
const track = object(format.audioTrack) ? format.audioTrack : null;
|
|
246
|
+
if (track && /\boriginal\b/i.test(string(track.displayName) ?? "")) {
|
|
247
|
+
return 2;
|
|
248
|
+
}
|
|
249
|
+
return track?.audioIsDefault === true ? 1 : 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
238
252
|
function playerBody(id: string, session: YoutubeSession): Json {
|
|
239
253
|
return {
|
|
240
254
|
contentCheckOk: true,
|