@hyperframes/producer 0.8.193 → 0.8.195
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/distributed.js +86 -25
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +72 -66
- package/dist/index.js +139 -35
- package/dist/public-server.js +139 -35
- package/dist/services/htmlCompiler.d.ts +2 -1
- package/dist/services/render/artifactTransaction.d.ts +3 -1
- package/dist/services/renderOrchestrator.d.ts +4 -2
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1564,8 +1564,11 @@ function assertDurationWithinTolerance(expectedSeconds, probedSeconds, tolerance
|
|
|
1564
1564
|
`Render artifact is truncated: expected ${expectedSeconds.toFixed(3)}s, probed ${probedSeconds.toFixed(3)}s (deficit ${deficit.toFixed(3)}s exceeds tolerance ${toleranceSeconds.toFixed(3)}s). Artifact: ${stagingPath}`
|
|
1565
1565
|
);
|
|
1566
1566
|
}
|
|
1567
|
+
function hasUsableFrameCount(frames) {
|
|
1568
|
+
return frames !== void 0 && Number.isFinite(frames) && frames > 0;
|
|
1569
|
+
}
|
|
1567
1570
|
function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPath, toleranceFrames = 1) {
|
|
1568
|
-
if (
|
|
1571
|
+
if (!hasUsableFrameCount(probedFrames)) {
|
|
1569
1572
|
return;
|
|
1570
1573
|
}
|
|
1571
1574
|
const shortfall = expectedFrames - probedFrames;
|
|
@@ -1585,9 +1588,10 @@ function assertSizeMatches(expected, probed, stagingPath) {
|
|
|
1585
1588
|
);
|
|
1586
1589
|
}
|
|
1587
1590
|
var ArtifactTransaction = class {
|
|
1588
|
-
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1591
|
+
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe, logger = defaultLogger) {
|
|
1589
1592
|
this.kind = kind;
|
|
1590
1593
|
this.fileSystem = fileSystem;
|
|
1594
|
+
this.logger = logger;
|
|
1591
1595
|
this.destinationPath = resolve4(destinationPath);
|
|
1592
1596
|
this.transactionDirectory = createSiblingTransactionDirectory(this.destinationPath);
|
|
1593
1597
|
this.stagingPath = join5(this.transactionDirectory, basename3(this.destinationPath));
|
|
@@ -1670,6 +1674,19 @@ var ArtifactTransaction = class {
|
|
|
1670
1674
|
probePath
|
|
1671
1675
|
);
|
|
1672
1676
|
if (expected.expectedFrames !== void 0) {
|
|
1677
|
+
if (!hasUsableFrameCount(probed.frames)) {
|
|
1678
|
+
try {
|
|
1679
|
+
this.logger.debug(
|
|
1680
|
+
"[Render] Frame-count validation skipped: no usable frame count was reported.",
|
|
1681
|
+
{
|
|
1682
|
+
event: "artifact_frame_count_unavailable",
|
|
1683
|
+
artifact: this.destinationPath,
|
|
1684
|
+
expectedFrames: expected.expectedFrames
|
|
1685
|
+
}
|
|
1686
|
+
);
|
|
1687
|
+
} catch {
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1673
1690
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1674
1691
|
}
|
|
1675
1692
|
assertSizeMatches(expected, probed, probePath);
|
|
@@ -5252,12 +5269,15 @@ import {
|
|
|
5252
5269
|
readMediaStart,
|
|
5253
5270
|
redactTelemetryString as redactTelemetryString2,
|
|
5254
5271
|
resolveNaturalMediaTimelineDurationFromValues,
|
|
5272
|
+
rewriteAssetPath,
|
|
5255
5273
|
rewriteAssetPaths,
|
|
5256
5274
|
rewriteCssAssetUrls,
|
|
5257
5275
|
rewriteInlineStyleAssetUrls
|
|
5258
5276
|
} from "@hyperframes/core";
|
|
5259
5277
|
import { MAX_AUDIO_GAIN } from "@hyperframes/core/audio-gain";
|
|
5260
|
-
import {
|
|
5278
|
+
import { readPlaybackRate } from "@hyperframes/parsers/media-duration";
|
|
5279
|
+
import { resolveRateSpec } from "@hyperframes/core/speed-ramp";
|
|
5280
|
+
import { gsapCdnDist, normalizeGsapBaseUrl } from "@hyperframes/core/gsap-cdn";
|
|
5261
5281
|
import {
|
|
5262
5282
|
assignBundledRuntimeCompositionIds,
|
|
5263
5283
|
assignMediaRenderIds,
|
|
@@ -6290,7 +6310,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6290
6310
|
const results = await Promise.all(
|
|
6291
6311
|
workItems.map(async (item) => {
|
|
6292
6312
|
const { html: compiledSub } = await compileHtmlFile(
|
|
6293
|
-
item.rawSubHtml,
|
|
6313
|
+
rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
|
|
6294
6314
|
dirname6(item.filePath),
|
|
6295
6315
|
downloadDir,
|
|
6296
6316
|
void 0,
|
|
@@ -6464,7 +6484,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
6464
6484
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
6465
6485
|
if (!compHtml) {
|
|
6466
6486
|
const read = readProjectFile(resolve6(projectDir, srcPath));
|
|
6467
|
-
if (read.kind === "file")
|
|
6487
|
+
if (read.kind === "file") {
|
|
6488
|
+
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
6489
|
+
}
|
|
6468
6490
|
}
|
|
6469
6491
|
return compHtml;
|
|
6470
6492
|
},
|
|
@@ -7007,18 +7029,27 @@ async function embedLocalFontFaces(html, projectDir) {
|
|
|
7007
7029
|
}
|
|
7008
7030
|
return result;
|
|
7009
7031
|
}
|
|
7010
|
-
|
|
7011
|
-
|
|
7032
|
+
function renderGsapDist() {
|
|
7033
|
+
try {
|
|
7034
|
+
return gsapCdnDist(void 0, normalizeGsapBaseUrl(process.env.HYPERFRAMES_GSAP_BASE_URL));
|
|
7035
|
+
} catch (cause) {
|
|
7036
|
+
throw new Error(
|
|
7037
|
+
"Invalid HYPERFRAMES_GSAP_BASE_URL: use an absolute HTTPS npm mirror root (plain HTTP only for localhost) without credentials, query, or fragment",
|
|
7038
|
+
{ cause }
|
|
7039
|
+
);
|
|
7040
|
+
}
|
|
7041
|
+
}
|
|
7042
|
+
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
7043
|
+
let gsapDist;
|
|
7044
|
+
const exists = (path) => existsSync10(resolve6(projectDir, path));
|
|
7012
7045
|
return html.replace(
|
|
7013
7046
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
7014
7047
|
(full, prefix, src, file, suffix) => {
|
|
7015
7048
|
if (/^https?:\/\//i.test(src)) return full;
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
defaultLogger.info(
|
|
7019
|
-
|
|
7020
|
-
);
|
|
7021
|
-
return `${prefix}${GSAP_CDN_BASE}${file}${suffix}`;
|
|
7049
|
+
if (exists(compSrcPath ? rewriteAssetPath(compSrcPath, src, exists) : src)) return full;
|
|
7050
|
+
gsapDist ??= renderGsapDist();
|
|
7051
|
+
defaultLogger.info(`[Compiler] Rewriting missing gsap script: ${src} \u2192 ${gsapDist}${file}`);
|
|
7052
|
+
return `${prefix}${gsapDist}${file}${suffix}`;
|
|
7022
7053
|
}
|
|
7023
7054
|
);
|
|
7024
7055
|
}
|
|
@@ -7232,6 +7263,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7232
7263
|
const intrinsicDuration = isImage ? 0 : htmlEl.duration;
|
|
7233
7264
|
const playbackStartRaw = htmlEl.getAttribute("data-playback-start");
|
|
7234
7265
|
const mediaStartRaw = htmlEl.getAttribute("data-media-start");
|
|
7266
|
+
const playbackRateRaw = htmlEl.getAttribute("data-playback-rate");
|
|
7267
|
+
const automationRaw = htmlEl.getAttribute("data-automation");
|
|
7268
|
+
const defaultPlaybackRate = isImage ? 1 : htmlEl.defaultPlaybackRate;
|
|
7235
7269
|
const loop = htmlEl.hasAttribute("loop");
|
|
7236
7270
|
const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
|
|
7237
7271
|
const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
|
|
@@ -7247,6 +7281,9 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7247
7281
|
durationInferred,
|
|
7248
7282
|
playbackStartRaw,
|
|
7249
7283
|
mediaStartRaw,
|
|
7284
|
+
playbackRateRaw,
|
|
7285
|
+
automationRaw,
|
|
7286
|
+
defaultPlaybackRate,
|
|
7250
7287
|
loop,
|
|
7251
7288
|
hasAudio,
|
|
7252
7289
|
volume,
|
|
@@ -7256,7 +7293,17 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7256
7293
|
return results;
|
|
7257
7294
|
});
|
|
7258
7295
|
return elements.map(
|
|
7259
|
-
({
|
|
7296
|
+
({
|
|
7297
|
+
endRaw,
|
|
7298
|
+
durationRaw,
|
|
7299
|
+
intrinsicDuration,
|
|
7300
|
+
playbackStartRaw,
|
|
7301
|
+
mediaStartRaw,
|
|
7302
|
+
playbackRateRaw,
|
|
7303
|
+
automationRaw,
|
|
7304
|
+
defaultPlaybackRate,
|
|
7305
|
+
...element
|
|
7306
|
+
}) => ({
|
|
7260
7307
|
...element,
|
|
7261
7308
|
end: parseStrictFiniteTimingNumber(endRaw) ?? 0,
|
|
7262
7309
|
duration: element.durationInferred && Number.isFinite(intrinsicDuration) && intrinsicDuration > 0 ? intrinsicDuration : parseStrictFiniteTimingNumber(durationRaw) ?? 0,
|
|
@@ -7266,7 +7313,14 @@ async function discoverMediaFromBrowser(page) {
|
|
|
7266
7313
|
if (name === "data-media-start") return mediaStartRaw;
|
|
7267
7314
|
return null;
|
|
7268
7315
|
}
|
|
7269
|
-
})
|
|
7316
|
+
}),
|
|
7317
|
+
playbackRate: resolveRateSpec(
|
|
7318
|
+
automationRaw,
|
|
7319
|
+
readPlaybackRate(
|
|
7320
|
+
(name) => name === "data-playback-rate" ? playbackRateRaw : null,
|
|
7321
|
+
defaultPlaybackRate
|
|
7322
|
+
)
|
|
7323
|
+
)
|
|
7270
7324
|
})
|
|
7271
7325
|
);
|
|
7272
7326
|
}
|
|
@@ -7686,9 +7740,18 @@ import {
|
|
|
7686
7740
|
getCompositionDuration,
|
|
7687
7741
|
initializeSession as initializeSession4,
|
|
7688
7742
|
isTransientBrowserError,
|
|
7689
|
-
probeBeginFrameLiveness
|
|
7743
|
+
probeBeginFrameLiveness,
|
|
7744
|
+
videoAudioId
|
|
7690
7745
|
} from "@hyperframes/engine";
|
|
7691
|
-
import {
|
|
7746
|
+
import {
|
|
7747
|
+
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
7748
|
+
fpsToNumber as fpsToNumber7,
|
|
7749
|
+
normalizeRateSpec as normalizeRateSpec3,
|
|
7750
|
+
readMediaStart as readMediaStart2,
|
|
7751
|
+
shiftRateLane as shiftRateLane2,
|
|
7752
|
+
sourceTimeAt as sourceTimeAt3,
|
|
7753
|
+
timeAtSourceTime
|
|
7754
|
+
} from "@hyperframes/core";
|
|
7692
7755
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
7693
7756
|
|
|
7694
7757
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -7747,16 +7810,28 @@ function authoredMediaStarts(html) {
|
|
|
7747
7810
|
}
|
|
7748
7811
|
return starts;
|
|
7749
7812
|
}
|
|
7750
|
-
function
|
|
7813
|
+
function isVideoAudio(audio, videoId) {
|
|
7814
|
+
return audio.type === "video" && audio.id === videoAudioId(videoId);
|
|
7815
|
+
}
|
|
7816
|
+
function adoptRuntimeMediaTiming(composition, el, authored) {
|
|
7751
7817
|
const shift = el.mediaStart - authored;
|
|
7752
|
-
if (el.mediaStart <= 0 || Math.abs(shift) <= BROWSER_MEDIA_EPSILON) return;
|
|
7753
7818
|
const entries = el.tagName === "video" ? [
|
|
7754
7819
|
...composition.videos.filter((video) => video.id === el.id),
|
|
7755
|
-
...composition.audios.filter(
|
|
7756
|
-
(audio) => audio.type === "video" && audio.id === `${el.id}-audio`
|
|
7757
|
-
)
|
|
7820
|
+
...composition.audios.filter((audio) => isVideoAudio(audio, el.id))
|
|
7758
7821
|
] : composition.audios.filter((audio) => audio.type !== "video" && audio.id === el.id);
|
|
7759
|
-
for (const entry of entries)
|
|
7822
|
+
for (const entry of entries) {
|
|
7823
|
+
if (typeof entry.playbackRate !== "object") {
|
|
7824
|
+
if (typeof el.playbackRate === "object") {
|
|
7825
|
+
const consumed = Math.max(0, entry.mediaStart - authored);
|
|
7826
|
+
const head = timeAtSourceTime(normalizeRateSpec3(entry.playbackRate), consumed);
|
|
7827
|
+
entry.mediaStart += sourceTimeAt3(el.playbackRate, head) - consumed;
|
|
7828
|
+
entry.playbackRate = shiftRateLane2(el.playbackRate, head);
|
|
7829
|
+
} else {
|
|
7830
|
+
entry.playbackRate = el.playbackRate;
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
if (el.mediaStart > 0 && Math.abs(shift) > BROWSER_MEDIA_EPSILON) entry.mediaStart += shift;
|
|
7834
|
+
}
|
|
7760
7835
|
}
|
|
7761
7836
|
function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, durationInferred) {
|
|
7762
7837
|
if (projectedEnd <= 0) return existingEnd;
|
|
@@ -8040,7 +8115,7 @@ async function runProbeStage(input) {
|
|
|
8040
8115
|
sourceChanged,
|
|
8041
8116
|
el.durationInferred
|
|
8042
8117
|
);
|
|
8043
|
-
|
|
8118
|
+
adoptRuntimeMediaTiming(
|
|
8044
8119
|
composition,
|
|
8045
8120
|
el,
|
|
8046
8121
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8063,6 +8138,7 @@ async function runProbeStage(input) {
|
|
|
8063
8138
|
start: el.start,
|
|
8064
8139
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8065
8140
|
mediaStart: el.mediaStart,
|
|
8141
|
+
playbackRate: el.playbackRate,
|
|
8066
8142
|
loop: el.loop,
|
|
8067
8143
|
hasAudio: el.hasAudio && !el.muted
|
|
8068
8144
|
});
|
|
@@ -8087,7 +8163,7 @@ async function runProbeStage(input) {
|
|
|
8087
8163
|
sourceChanged,
|
|
8088
8164
|
el.durationInferred
|
|
8089
8165
|
);
|
|
8090
|
-
|
|
8166
|
+
adoptRuntimeMediaTiming(
|
|
8091
8167
|
composition,
|
|
8092
8168
|
el,
|
|
8093
8169
|
authoredStarts.get(el.id) ?? existing.mediaStart
|
|
@@ -8105,6 +8181,7 @@ async function runProbeStage(input) {
|
|
|
8105
8181
|
end: resolveBrowserMediaEnd(el.start, el.end, el.duration),
|
|
8106
8182
|
mediaStart: el.mediaStart,
|
|
8107
8183
|
loop: el.loop,
|
|
8184
|
+
playbackRate: el.playbackRate,
|
|
8108
8185
|
layer: 0,
|
|
8109
8186
|
volume: el.volume,
|
|
8110
8187
|
type: "audio"
|
|
@@ -8365,7 +8442,8 @@ import {
|
|
|
8365
8442
|
isHttpUrl as isHttpUrl2,
|
|
8366
8443
|
resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
|
|
8367
8444
|
runVideoExtractionWithRetry,
|
|
8368
|
-
safeVideoExtractionSourceIdentity
|
|
8445
|
+
safeVideoExtractionSourceIdentity,
|
|
8446
|
+
videoAudioId as videoAudioId2
|
|
8369
8447
|
} from "@hyperframes/engine";
|
|
8370
8448
|
|
|
8371
8449
|
// src/services/render/extractionFailureMetadata.ts
|
|
@@ -8723,7 +8801,7 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
8723
8801
|
const video = composition.videos.find((v) => v.id === ext.videoId);
|
|
8724
8802
|
if (!video || !video.hasAudio || video.hidden || existingAudioSrcs.has(video.src)) continue;
|
|
8725
8803
|
composition.audios.push({
|
|
8726
|
-
id:
|
|
8804
|
+
id: videoAudioId2(video.id),
|
|
8727
8805
|
src: video.src,
|
|
8728
8806
|
start: video.start,
|
|
8729
8807
|
end: video.end,
|
|
@@ -11749,13 +11827,36 @@ function colorGradingValueHasLut(rawAttributeValue) {
|
|
|
11749
11827
|
return false;
|
|
11750
11828
|
}
|
|
11751
11829
|
}
|
|
11752
|
-
|
|
11830
|
+
var PROBE_READ_TIMEOUT_MS = 5e3;
|
|
11831
|
+
async function evaluateWithinBound(page, fn, what, log) {
|
|
11832
|
+
let timer;
|
|
11833
|
+
const timedOut = new Promise((_, reject) => {
|
|
11834
|
+
timer = setTimeout(
|
|
11835
|
+
() => reject(new Error(`no answer within ${PROBE_READ_TIMEOUT_MS} ms (page busy)`)),
|
|
11836
|
+
PROBE_READ_TIMEOUT_MS
|
|
11837
|
+
);
|
|
11838
|
+
});
|
|
11839
|
+
try {
|
|
11840
|
+
return await Promise.race([page.evaluate(fn), timedOut]);
|
|
11841
|
+
} catch (err) {
|
|
11842
|
+
log.warn(`[Render] live ${what} read skipped; using the static answer`, {
|
|
11843
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11844
|
+
});
|
|
11845
|
+
throw err;
|
|
11846
|
+
} finally {
|
|
11847
|
+
clearTimeout(timer);
|
|
11848
|
+
}
|
|
11849
|
+
}
|
|
11850
|
+
async function resolveCompositionElementCount(probeSession, html, log = defaultLogger) {
|
|
11753
11851
|
if (probeSession?.isInitialized) {
|
|
11754
11852
|
try {
|
|
11755
|
-
const liveCount = await
|
|
11853
|
+
const liveCount = await evaluateWithinBound(
|
|
11854
|
+
probeSession.page,
|
|
11756
11855
|
// Live HTMLCollection length — avoids materializing a static NodeList
|
|
11757
11856
|
// on the large-DOM comps this gate exists to catch (review nit).
|
|
11758
|
-
() => document.getElementsByTagName("*").length
|
|
11857
|
+
() => document.getElementsByTagName("*").length,
|
|
11858
|
+
"element count",
|
|
11859
|
+
log
|
|
11759
11860
|
);
|
|
11760
11861
|
if (typeof liveCount === "number" && Number.isFinite(liveCount)) {
|
|
11761
11862
|
return { count: liveCount, source: "live" };
|
|
@@ -11843,11 +11944,11 @@ function probeAdaptersUsed() {
|
|
|
11843
11944
|
}
|
|
11844
11945
|
return used;
|
|
11845
11946
|
}
|
|
11846
|
-
async function resolveAdaptersUsed(probeSession, html) {
|
|
11947
|
+
async function resolveAdaptersUsed(probeSession, html, log = defaultLogger) {
|
|
11847
11948
|
const staticAdapters = detectAdaptersStatic(html);
|
|
11848
11949
|
if (!probeSession?.isInitialized) return staticAdapters;
|
|
11849
11950
|
try {
|
|
11850
|
-
const live = await probeSession.page
|
|
11951
|
+
const live = await evaluateWithinBound(probeSession.page, probeAdaptersUsed, "adapters", log);
|
|
11851
11952
|
if (!Array.isArray(live)) return staticAdapters;
|
|
11852
11953
|
const detected = /* @__PURE__ */ new Set([...staticAdapters, ...live]);
|
|
11853
11954
|
return KNOWN_RUNTIME_ADAPTERS.filter((name) => detected.has(name));
|
|
@@ -12105,7 +12206,10 @@ async function executeRenderPipeline(input) {
|
|
|
12105
12206
|
);
|
|
12106
12207
|
const artifactTransaction = new ArtifactTransaction(
|
|
12107
12208
|
outputPath,
|
|
12108
|
-
isPngSequence || isHls ? "directory" : "file"
|
|
12209
|
+
isPngSequence || isHls ? "directory" : "file",
|
|
12210
|
+
void 0,
|
|
12211
|
+
void 0,
|
|
12212
|
+
log
|
|
12109
12213
|
);
|
|
12110
12214
|
const stagedOutputPath = artifactTransaction.stagingPath;
|
|
12111
12215
|
const needsAlpha = outputNeedsAlpha(outputFormat);
|
|
@@ -12604,8 +12708,8 @@ async function executeRenderPipeline(input) {
|
|
|
12604
12708
|
vfxHostCount,
|
|
12605
12709
|
vfxCapture,
|
|
12606
12710
|
vfxTypes
|
|
12607
|
-
} = await resolveCompositionElementCount(probeSession, compiled.html);
|
|
12608
|
-
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html);
|
|
12711
|
+
} = await resolveCompositionElementCount(probeSession, compiled.html, log);
|
|
12712
|
+
const adaptersUsed = await resolveAdaptersUsed(probeSession, compiled.html, log);
|
|
12609
12713
|
const deShortBandEnabled = deShortBandMaxElements > 0;
|
|
12610
12714
|
const deShortBandOpen = deShortBandEnabled && deShortBandMinFrames > 0 && compositionElementCountSource === "live" && compositionElementCount <= deShortBandMaxElements;
|
|
12611
12715
|
const deShortBandRoute = process.env.HF_DE_SHORT_BAND_ROUTE === "true";
|