Skip to content

Commit bf95ebc

Browse files
committed
app: clean up and update request types
1 parent 2a1aaef commit bf95ebc

File tree

11 files changed

+273
-253
lines changed

11 files changed

+273
-253
lines changed

api/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"express": "^4.21.2",
3333
"express-rate-limit": "^7.4.1",
3434
"ffmpeg-static": "^5.1.0",
35-
"ffprobe-static": "^3.1.0",
3635
"hls-parser": "^0.10.7",
3736
"ipaddr.js": "2.2.0",
3837
"mime": "^4.0.4",

api/src/processing/match.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,17 @@ export default async function({ host, patternMatch, params, authType }) {
330330
const lpEnv = env.forceLocalProcessing;
331331
const shouldForceLocal = lpEnv === "always" || (lpEnv === "session" && authType === "session");
332332
const localDisabled = (!localProcessing || localProcessing === "none");
333+
const isClip = typeof params.clipStart === 'number' && typeof params.clipEnd === 'number';
333334

334335
if (shouldForceLocal && localDisabled) {
335336
localProcessing = "preferred";
336337
}
337338

339+
if (isClip) {
340+
r.clipStart = params.clipStart;
341+
r.clipEnd = params.clipEnd;
342+
}
343+
338344
return matchAction({
339345
r,
340346
host,

api/src/processing/services/youtube.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,6 @@ export default async function (o) {
291291
return { error: "content.too_long" };
292292
}
293293

294-
if (typeof o.clipStart === 'number' && o.clipStart >= basicInfo.duration) {
295-
return { error: "clip.start_exceeds_duration" };
296-
}
297-
if (typeof o.clipEnd === 'number' && o.clipEnd > basicInfo.duration) {
298-
return { error: "clip.end_exceeds_duration" };
299-
}
300-
301294
// return a critical error if returned video is "Video Not Available"
302295
// or a similar stub by youtube
303296
if (basicInfo.id !== o.id) {
@@ -581,8 +574,6 @@ export default async function (o) {
581574

582575
cover,
583576
cropCover: basicInfo.author.endsWith("- Topic"),
584-
clipStart: o.clipStart,
585-
clipEnd: o.clipEnd,
586577
}
587578
}
588579

@@ -628,9 +619,7 @@ export default async function (o) {
628619
fileMetadata,
629620
isHLS: useHLS,
630621
originalRequest,
631-
duration: basicInfo.duration,
632-
clipStart: o.clipStart,
633-
clipEnd: o.clipEnd,
622+
duration: basicInfo.duration
634623
}
635624
}
636625

api/src/stream/ffmpeg.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ const remux = async (streamInfo, res) => {
100100
const format = streamInfo.filename.split('.').pop();
101101
const urls = Array.isArray(streamInfo.urls) ? streamInfo.urls : [streamInfo.urls];
102102
const isClipping = typeof streamInfo.clipStart === 'number' || typeof streamInfo.clipEnd === 'number';
103-
let args = [];
104-
105-
args.push(...urls.flatMap(url => ['-i', url]));
103+
const args = urls.flatMap(url => ['-i', url]);
106104

107105
if (typeof streamInfo.clipStart === 'number') {
108106
args.push('-ss', streamInfo.clipStart.toString());
@@ -171,13 +169,11 @@ const remux = async (streamInfo, res) => {
171169
}
172170

173171
const convertAudio = async (streamInfo, res) => {
174-
let args = [];
175-
176-
args.push(
172+
const args = [
177173
'-i', streamInfo.urls,
178174
'-vn',
179175
...(streamInfo.audioCopy ? ['-c:a', 'copy'] : ['-b:a', `${streamInfo.audioBitrate}k`]),
180-
);
176+
];
181177

182178
if (typeof streamInfo.clipStart === 'number') {
183179
args.push('-ss', streamInfo.clipStart.toString());

pnpm-lock.yaml

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<script lang="ts">
2+
import ClipRangeSlider from "./ClipRangeSlider.svelte";
3+
4+
let {
5+
metaLoading,
6+
metaError,
7+
metadata,
8+
clipStart = $bindable(),
9+
clipEnd = $bindable()
10+
} = $props<{
11+
metaLoading: boolean;
12+
metaError: string | null;
13+
metadata: { title?: string; author?: string; duration?: number } | null;
14+
clipStart: number;
15+
clipEnd: number;
16+
}>();
17+
18+
function formatTime(seconds: number) {
19+
const m = Math.floor(seconds / 60);
20+
const s = (seconds % 60).toFixed(3).padStart(6, '0');
21+
return `${m}:${s}`;
22+
}
23+
</script>
24+
25+
<div class="clip-controls">
26+
{#if metaLoading}
27+
<div class="loading-state">
28+
<div class="loading-spinner"></div>
29+
<span>Loading video metadata...</span>
30+
</div>
31+
{:else if metaError}
32+
<div class="error-state">
33+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34+
<circle cx="12" cy="12" r="10"></circle>
35+
<line x1="15" y1="9" x2="9" y2="15"></line>
36+
<line x1="9" y1="9" x2="15" y2="15"></line>
37+
</svg>
38+
<span>{metaError}</span>
39+
</div>
40+
{:else if metadata}
41+
<div class="clip-metadata">
42+
<div class="metadata-header">
43+
<h4>Clip</h4>
44+
<div class="duration-badge">
45+
{typeof metadata.duration === 'number' ? formatTime(metadata.duration) : 'Unknown'}
46+
</div>
47+
</div>
48+
<div class="video-info">
49+
<div class="video-title" title={metadata.title}>
50+
{metadata.title}
51+
</div>
52+
{#if metadata.author}
53+
<div class="video-author">
54+
by {metadata.author}
55+
</div>
56+
{/if}
57+
</div>
58+
</div>
59+
60+
<ClipRangeSlider
61+
min={0}
62+
max={metadata.duration || 0}
63+
step={0.001}
64+
bind:start={clipStart}
65+
bind:end={clipEnd}
66+
/>
67+
68+
<div class="clip-time-display">
69+
<div class="time-indicators">
70+
<span class="start-time">{formatTime(clipStart)}</span>
71+
<span class="duration-selected">
72+
{formatTime(Math.max(0, clipEnd - clipStart))} selected
73+
</span>
74+
<span class="end-time">{formatTime(clipEnd)}</span>
75+
</div>
76+
</div>
77+
{/if}
78+
</div>
79+
80+
<style>
81+
.clip-controls {
82+
margin-top: 12px;
83+
padding: 16px;
84+
background: var(--button);
85+
border-radius: var(--border-radius);
86+
border: 1px solid var(--button-stroke);
87+
animation: slideIn 0.3s cubic-bezier(0.2, 0, 0, 1);
88+
}
89+
90+
.loading-state {
91+
display: flex;
92+
align-items: center;
93+
gap: 12px;
94+
padding: 16px 0;
95+
color: var(--gray);
96+
font-size: 14px;
97+
}
98+
99+
.loading-spinner {
100+
width: 16px;
101+
height: 16px;
102+
border: 2px solid var(--button-hover);
103+
border-top: 2px solid var(--secondary);
104+
border-radius: 50%;
105+
animation: spin 1s linear infinite;
106+
}
107+
108+
.error-state {
109+
display: flex;
110+
align-items: center;
111+
gap: 8px;
112+
padding: 12px 16px;
113+
background: rgba(237, 34, 54, 0.1);
114+
border: 1px solid rgba(237, 34, 54, 0.2);
115+
border-radius: 8px;
116+
color: var(--red);
117+
font-size: 13px;
118+
font-weight: 500;
119+
}
120+
121+
.clip-metadata {
122+
margin-bottom: 16px;
123+
}
124+
125+
.metadata-header {
126+
display: flex;
127+
align-items: center;
128+
justify-content: space-between;
129+
margin-bottom: 12px;
130+
padding-bottom: 8px;
131+
}
132+
133+
.metadata-header h4 {
134+
margin: 0;
135+
font-size: 15px;
136+
font-weight: 600;
137+
color: var(--secondary);
138+
}
139+
140+
.duration-badge {
141+
background: var(--button-elevated);
142+
color: var(--button-text);
143+
padding: 4px 8px;
144+
border-radius: 6px;
145+
font-size: 11px;
146+
font-weight: 600;
147+
font-family: 'IBM Plex Mono', monospace;
148+
}
149+
150+
.video-info {
151+
display: flex;
152+
flex-direction: column;
153+
gap: 4px;
154+
}
155+
156+
.video-title {
157+
font-size: 14px;
158+
font-weight: 500;
159+
color: var(--secondary);
160+
overflow: hidden;
161+
text-overflow: ellipsis;
162+
white-space: nowrap;
163+
max-width: 100%;
164+
}
165+
166+
.video-author {
167+
font-size: 12px;
168+
color: var(--gray);
169+
font-weight: 400;
170+
}
171+
172+
.clip-time-display {
173+
margin-top: 8px;
174+
}
175+
176+
.time-indicators {
177+
display: flex;
178+
justify-content: space-between;
179+
align-items: center;
180+
font-size: 13px;
181+
font-weight: 500;
182+
font-family: 'IBM Plex Mono', monospace;
183+
}
184+
185+
.start-time, .end-time {
186+
color: var(--gray);
187+
background: var(--button-hover);
188+
padding: 2px 6px;
189+
border-radius: 4px;
190+
min-width: 50px;
191+
text-align: center;
192+
}
193+
194+
.duration-selected {
195+
grid-area: duration;
196+
text-align: center;
197+
}
198+
199+
@keyframes slideIn {
200+
from {
201+
opacity: 0;
202+
transform: translateY(-10px);
203+
}
204+
to {
205+
opacity: 1;
206+
transform: translateY(0);
207+
}
208+
}
209+
210+
@keyframes spin {
211+
0% { transform: rotate(0deg); }
212+
100% { transform: rotate(360deg); }
213+
}
214+
215+
@media screen and (max-width: 440px) {
216+
.clip-controls {
217+
padding: 12px;
218+
}
219+
220+
.metadata-header {
221+
flex-direction: column;
222+
align-items: flex-start;
223+
gap: 8px;
224+
}
225+
226+
.video-title {
227+
white-space: normal;
228+
}
229+
230+
.time-indicators {
231+
display: grid;
232+
grid-template-columns: 1fr 1fr;
233+
grid-template-areas:
234+
"start end"
235+
"duration duration";
236+
gap: 8px;
237+
}
238+
239+
.start-time {
240+
grid-area: start;
241+
}
242+
243+
.end-time {
244+
grid-area: end;
245+
}
246+
}
247+
</style>

web/src/components/save/ClipRangeSlider.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,4 @@
345345
transition: none;
346346
}
347347
}
348-
</style>
348+
</style>

0 commit comments

Comments
 (0)