@torrent-tv/proxy 2.95.4 → 2.95.5
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/package.json
CHANGED
package/server.js
CHANGED
|
@@ -467,9 +467,7 @@ export async function startProxyServer({
|
|
|
467
467
|
const navigation = await containerOrchestrator.inspect(intervalParams, "source-navigation");
|
|
468
468
|
if (navigation.kind !== "result") continue;
|
|
469
469
|
const originalRanges = navigation.value;
|
|
470
|
-
const packets = originalRanges
|
|
471
|
-
? { kind: "result", value: await container.readMappedSourceRanges(interval) }
|
|
472
|
-
: await containerOrchestrator.inspect(intervalParams, originalRanges ? "source-ranges" : "packets");
|
|
470
|
+
const packets = await containerOrchestrator.inspect(intervalParams, originalRanges ? "source-ranges" : "packets");
|
|
473
471
|
if (!map.isCurrent()) return [];
|
|
474
472
|
if (packets.kind !== "result") continue;
|
|
475
473
|
const input = originalRanges ? packets.value : new SegmentInputs({ index: packets.value, tracks: wanted }).forInterval({ ...interval, mode: track => modes.get(track) });
|
|
@@ -64,6 +64,13 @@ export function buildRunCommand({
|
|
|
64
64
|
const audioCodecArgs = transcodeAudio
|
|
65
65
|
? ["-c:a", "aac", "-ac", "2", "-b:a", `${AUDIO_TRANSCODE_KBPS}k`]
|
|
66
66
|
: ["-c:a", "copy"];
|
|
67
|
+
// Under `-copyts` FFmpeg trims a decoded stream at `-ss` PLUS its own idea of
|
|
68
|
+
// the file's start, ignoring `-seek_timestamp` (ffmpeg_demux.c, n8.1.2,
|
|
69
|
+
// `trim_start_us`): an absolute seek would be trimmed a second start later.
|
|
70
|
+
// On the keyframe grid its trim is therefore switched off on every input and
|
|
71
|
+
// the decoded soundtrack is cut here, on the picture file's clock, which is
|
|
72
|
+
// the clock every stream carries there.
|
|
73
|
+
const inputSeekArgs = keyframeGrid ? ["-noaccurate_seek"] : ["-accurate_seek"];
|
|
67
74
|
|
|
68
75
|
const args = ["-hide_banner", "-nostats", "-loglevel", "error", "-progress", "pipe:1"];
|
|
69
76
|
if (transcodeVideo && Array.isArray(videoEncoder.inputArgs)) {
|
|
@@ -71,16 +78,23 @@ export function buildRunCommand({
|
|
|
71
78
|
}
|
|
72
79
|
const inputStartSeconds = inputIndex === safeIndex ? startSeconds :
|
|
73
80
|
publishedStartTime(timeline, inputIndex, segmentDurationSec);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
// ONE CLOCK FOR EVERY INPUT SEEK. Each `-ss` below is an absolute timestamp
|
|
82
|
+
// of the file it seeks, given with `-seek_timestamp 1`: without it FFmpeg
|
|
83
|
+
// adds its own idea of the file's start time to the value, which is a second
|
|
84
|
+
// copy of a start this code already knows. Published positions begin at
|
|
85
|
+
// zero, so the picture's instant is the published time plus the picture
|
|
86
|
+
// file's start; keyframe times are on the same file clock; a soundtrack in
|
|
87
|
+
// another file is the same instant on that file's clock. Whether the cuts
|
|
88
|
+
// follow the source's keyframes or an even grid changes how the run ENDS
|
|
89
|
+
// and what its output timestamps are, never where its input is.
|
|
90
|
+
const seekAt = inputStartSeconds + sourceStartTime;
|
|
77
91
|
const carriedKeyframe = timeline.cutGrid === "keyframe" && typeof timeline.sourceStartOf === "function"
|
|
78
92
|
? timeline.sourceStartOf(inputIndex)
|
|
79
93
|
: null;
|
|
80
94
|
const snappedKeyframe = Number.isFinite(carriedKeyframe)
|
|
81
95
|
? carriedKeyframe
|
|
82
96
|
: (Array.isArray(keyframes?.times) && keyframes.times.length > 0
|
|
83
|
-
? nearestKeyframeAtOrBefore(keyframes.times,
|
|
97
|
+
? nearestKeyframeAtOrBefore(keyframes.times, seekAt)
|
|
84
98
|
: null);
|
|
85
99
|
const audioInputUrl =
|
|
86
100
|
typeof audioInputUrlGiven === "string" && audioInputUrlGiven.length > 0
|
|
@@ -98,8 +112,8 @@ export function buildRunCommand({
|
|
|
98
112
|
* order wrong would silently turn the audio file's seek into a trim of the
|
|
99
113
|
* finished stream.
|
|
100
114
|
*
|
|
101
|
-
* @param {number} inputSeekSeconds - Where to start, on the PICTURE
|
|
102
|
-
*
|
|
115
|
+
* @param {number} inputSeekSeconds - Where to start, on the PICTURE
|
|
116
|
+
* file's clock. Translated to the soundtrack file's own here.
|
|
103
117
|
*/
|
|
104
118
|
const pushAudioInput = (inputSeekSeconds) => {
|
|
105
119
|
if (!audioInputUrl) {
|
|
@@ -108,17 +122,17 @@ export function buildRunCommand({
|
|
|
108
122
|
if (audioTimelineShift !== 0 && keyframeGrid) {
|
|
109
123
|
args.push("-itsoffset", ffmpegSeconds(-audioTimelineShift));
|
|
110
124
|
}
|
|
111
|
-
const audioSeek =
|
|
112
|
-
if (audioSeek >
|
|
113
|
-
args.push("-
|
|
125
|
+
const audioSeek = inputSeekSeconds + audioTimelineShift;
|
|
126
|
+
if (audioSeek > audioFileStartTime) {
|
|
127
|
+
args.push("-seek_timestamp", "1", ...inputSeekArgs, "-ss", ffmpegSeconds(audioSeek));
|
|
114
128
|
}
|
|
115
129
|
args.push("-i", audioInputUrl);
|
|
116
130
|
};
|
|
117
131
|
|
|
118
132
|
if (snappedKeyframe !== null) {
|
|
119
|
-
const residualSeconds = Math.max(0,
|
|
120
|
-
if (snappedKeyframe >
|
|
121
|
-
args.push("-ss", ffmpegSeconds(snappedKeyframe + seekLandingOffsetFor({ audioOnly, transcodeVideo, keyframes, reorderDepth }, snappedKeyframe)));
|
|
133
|
+
const residualSeconds = Math.max(0, seekAt - snappedKeyframe);
|
|
134
|
+
if (snappedKeyframe > sourceStartTime) {
|
|
135
|
+
args.push("-seek_timestamp", "1", ...inputSeekArgs, "-ss", ffmpegSeconds(snappedKeyframe + seekLandingOffsetFor({ audioOnly, transcodeVideo, keyframes, reorderDepth }, snappedKeyframe)));
|
|
122
136
|
}
|
|
123
137
|
args.push("-i", inputUrl);
|
|
124
138
|
pushAudioInput(snappedKeyframe);
|
|
@@ -126,11 +140,11 @@ export function buildRunCommand({
|
|
|
126
140
|
args.push("-ss", ffmpegSeconds(residualSeconds));
|
|
127
141
|
}
|
|
128
142
|
} else {
|
|
129
|
-
if (
|
|
130
|
-
args.push("-
|
|
143
|
+
if (seekAt > sourceStartTime) {
|
|
144
|
+
args.push("-seek_timestamp", "1", ...inputSeekArgs, "-ss", ffmpegSeconds(seekAt));
|
|
131
145
|
}
|
|
132
146
|
args.push("-i", inputUrl);
|
|
133
|
-
pushAudioInput(
|
|
147
|
+
pushAudioInput(seekAt);
|
|
134
148
|
}
|
|
135
149
|
if (!keyframeGrid) {
|
|
136
150
|
if (startSeconds > 0) {
|
|
@@ -146,12 +160,20 @@ export function buildRunCommand({
|
|
|
146
160
|
const publishedGrid = publishedGridFor(timeline);
|
|
147
161
|
if (runEnd >= safeIndex && Array.isArray(publishedGrid) && publishedGrid[runEnd + 1] > 0) {
|
|
148
162
|
const endsAt = publishedGrid[runEnd + 1];
|
|
149
|
-
if (
|
|
150
|
-
|
|
163
|
+
if (keyframeGrid) {
|
|
164
|
+
// `-copyts` keeps the input's own clock up to the muxer, and an output
|
|
165
|
+
// `-to` is compared there, before `-output_ts_offset` moves it.
|
|
166
|
+
args.push("-to", ffmpegSeconds(endsAt + sourceStartTime));
|
|
151
167
|
} else {
|
|
152
|
-
|
|
168
|
+
// The output clock starts at zero where this run begins, so the end is
|
|
169
|
+
// a length; an output `-to` would be read as a length from zero too and
|
|
170
|
+
// run past the interval by its own start.
|
|
171
|
+
args.push("-t", ffmpegSeconds(Math.max(0.1, endsAt - startSeconds)));
|
|
153
172
|
}
|
|
154
173
|
}
|
|
174
|
+
if (keyframeGrid && transcodeAudio && !servesAudioSeparately) {
|
|
175
|
+
audioCodecArgs.push("-af", `atrim=start=${ffmpegSeconds(seekAt)}`);
|
|
176
|
+
}
|
|
155
177
|
if (audioOnly === true) {
|
|
156
178
|
args.push("-vn", "-map", `0:a:${audioSourceTrackIndex}?`, ...audioCodecArgs);
|
|
157
179
|
} else if (servesAudioSeparately) {
|
|
@@ -217,7 +239,7 @@ export function buildOriginalCommand(params) {
|
|
|
217
239
|
if (!primary) throw new Error("The selected source track is absent from original input.");
|
|
218
240
|
const url = source => new URL(`/encode-input/${inputToken}/${source.fileIndex}`, baseUrl).href;
|
|
219
241
|
const localTimeline = { ...timeline, published: [from, to, to + (to - from)],
|
|
220
|
-
boundaries: [from, to, to + (to - from)], sourceStartOf: () =>
|
|
242
|
+
boundaries: [from, to, to + (to - from)], sourceStartOf: () => primary.input.from ?? from + primary.timeShiftSeconds };
|
|
221
243
|
const command = buildRunCommand({ ...params, startIndex: 0, endIndex: 0, timeline: localTimeline,
|
|
222
244
|
videoSourceTrackIndex: primary.input.selections.find(selection => selection.track.type === "video")?.index ?? 0,
|
|
223
245
|
reorderDepth: primary.input.selections.find(selection => selection.track.type === "video")?.track.reorderDepth ?? 0,
|
|
@@ -229,7 +251,7 @@ export function buildOriginalCommand(params) {
|
|
|
229
251
|
// the inner muxer writes its edit list, moving B-picture PTS off the source.
|
|
230
252
|
args.splice(args.indexOf("-f"), 0, "-avoid_negative_ts", "disabled");
|
|
231
253
|
const formatOptions = args.indexOf("-segment_format_options");
|
|
232
|
-
if (formatOptions >= 0) args[formatOptions + 1] += ":avoid_negative_ts=disabled";
|
|
254
|
+
if (formatOptions >= 0) args[formatOptions + 1] += ":avoid_negative_ts=disabled:movie_timescale=1000000";
|
|
233
255
|
const numbering = args.indexOf("-segment_start_number");
|
|
234
256
|
if (numbering < 0) throw new Error("The format cannot produce finite original-source segments.");
|
|
235
257
|
args[numbering + 1] = String(startIndex);
|
|
@@ -600,7 +600,7 @@ export class MatroskaContainer extends Container {
|
|
|
600
600
|
.filter(position => !video || position.track === video.trackNumber)
|
|
601
601
|
.map(position => ({ at: position.clusterAt, seconds: point.ticks * layout.secondsPerTick })))
|
|
602
602
|
.sort((left, right) => left.seconds - right.seconds || left.at - right.at);
|
|
603
|
-
if (!points.length) return
|
|
603
|
+
if (!points.length) return { kind: "needs-index", reason: "source-has-no-cue-map" };
|
|
604
604
|
const preroll = Math.max(0, ...requested.map(track => track.seekPrerollSeconds ?? 0));
|
|
605
605
|
const firstAt = (seconds, strict) => {
|
|
606
606
|
let low = 0, high = points.length;
|
|
@@ -632,7 +632,7 @@ export class MatroskaContainer extends Container {
|
|
|
632
632
|
[Math.max(0, this.fileSize - requestBytes), this.fileSize - 1]];
|
|
633
633
|
// SeekHead may name top-level declarations after media clusters. Preserve
|
|
634
634
|
// their original bytes instead of reconstructing declarations for FFmpeg.
|
|
635
|
-
for (const at of [layout.infoAt, layout.tracksAt]) {
|
|
635
|
+
for (const at of [layout.infoAt, layout.tracksAt, layout.tagsAt, layout.chaptersAt, layout.attachmentsAt]) {
|
|
636
636
|
if (at === null || at < layout.firstClusterAt) continue;
|
|
637
637
|
const end = await elementEnd(at);
|
|
638
638
|
if (end === null) return { kind: "terminal", reason: "source-declarations-are-unbounded" };
|