Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75129db86c | ||
|
|
8b79371d36 | ||
|
|
dca82a1c54 |
@@ -9,7 +9,24 @@ It retrieves metadata from Spotify and locates matching audio on YouTube.
|
|||||||
|
|
||||||
Deterministic Spotify metadata ingestion with duration-aware YouTube candidate scoring, paced queue execution, hardened Docker deployment, and local archival workflows.
|
Deterministic Spotify metadata ingestion with duration-aware YouTube candidate scoring, paced queue execution, hardened Docker deployment, and local archival workflows.
|
||||||
|
|
||||||
Current version: 3.0.0
|
Current version: 3.1.0
|
||||||
|
|
||||||
|
## What's New in 3.1.0
|
||||||
|
|
||||||
|
Jarri Spooty 3.1.0 introduces the first generation of operational truth contracts.
|
||||||
|
|
||||||
|
New capabilities include:
|
||||||
|
|
||||||
|
- Operations Snapshot API
|
||||||
|
- Candidate selection provenance
|
||||||
|
- Selected YouTube candidate persistence
|
||||||
|
- Rejected YouTube candidate persistence
|
||||||
|
- Candidate scoring visibility
|
||||||
|
- Candidate selection reasoning
|
||||||
|
- Improved Workspace diagnostics
|
||||||
|
- Backend-owned operational truth for future Workspace integrations
|
||||||
|
|
||||||
|
Spooty now exposes why a candidate was selected rather than treating YouTube matching as a black box.
|
||||||
|
|
||||||
This hardened branch focuses on:
|
This hardened branch focuses on:
|
||||||
|
|
||||||
@@ -31,7 +48,10 @@ This hardened branch focuses on:
|
|||||||
- Reduced unused dependency surface
|
- Reduced unused dependency surface
|
||||||
- Hardened filename, cover-art, subprocess, and websocket boundaries
|
- Hardened filename, cover-art, subprocess, and websocket boundaries
|
||||||
- Workspace Archive Browser MVP with scoped read-only archive listing
|
- Workspace Archive Browser MVP with scoped read-only archive listing
|
||||||
- Workspace Candidate Inspector MVP for selected track details
|
- Workspace Candidate Inspector with candidate provenance
|
||||||
|
- Operations Snapshot API
|
||||||
|
- Candidate scoring visibility
|
||||||
|
- Candidate rejection persistence
|
||||||
- Frontend archive destination field for planned per-run routing
|
- Frontend archive destination field for planned per-run routing
|
||||||
- Frontend Workspace tabs for Intake, Archive, and Diagnostics layouts
|
- Frontend Workspace tabs for Intake, Archive, and Diagnostics layouts
|
||||||
|
|
||||||
@@ -53,11 +73,15 @@ This hardened branch focuses on:
|
|||||||
- Automatic YouTube retry/fallback handling
|
- Automatic YouTube retry/fallback handling
|
||||||
- Failed YouTube candidate rejection memory
|
- Failed YouTube candidate rejection memory
|
||||||
- Deterministic yt-dlp error classification
|
- Deterministic yt-dlp error classification
|
||||||
|
- Candidate scoring visibility
|
||||||
|
- Candidate selection reasoning
|
||||||
|
- Candidate provenance persistence
|
||||||
- Hardened cover-art validation and embedding
|
- Hardened cover-art validation and embedding
|
||||||
- Explicit client-facing websocket payload shaping
|
- Explicit client-facing websocket payload shaping
|
||||||
- Draggable/resizable Workspace UI
|
- Draggable/resizable Workspace UI
|
||||||
- Archive Browser panel for downloaded file inspection
|
- Archive Browser panel for downloaded file inspection
|
||||||
- Candidate Inspector panel for selected track diagnostics
|
- Candidate Inspector panel for selected track diagnostics
|
||||||
|
- Operations Snapshot diagnostics API
|
||||||
- Frontend-persisted archive destination input
|
- Frontend-persisted archive destination input
|
||||||
- Per-tab workspace layout presets
|
- Per-tab workspace layout presets
|
||||||
- Read-only archive search, sort, and grouped file inspection
|
- Read-only archive search, sort, and grouped file inspection
|
||||||
@@ -66,7 +90,7 @@ This hardened branch focuses on:
|
|||||||
|
|
||||||
# Workspace UI
|
# Workspace UI
|
||||||
|
|
||||||
Jarri Spooty 3.0.0 introduces a draggable/resizable Workspace shell with frontend tab presets.
|
Jarri Spooty 3.1.0 introduces a draggable/resizable Workspace shell with frontend tab presets.
|
||||||
|
|
||||||
Workspace tabs:
|
Workspace tabs:
|
||||||
|
|
||||||
@@ -79,11 +103,11 @@ Each tab has its own persisted panel layout in browser localStorage. The old sin
|
|||||||
Workspace panels:
|
Workspace panels:
|
||||||
|
|
||||||
- Source Intake: Spotify connection, URL queueing, and a frontend-persisted Archive destination field
|
- Source Intake: Spotify connection, URL queueing, and a frontend-persisted Archive destination field
|
||||||
- Queue Observatory: Spotify, playlist, single-song, and archive-run metrics
|
- Queue Observatory: Spotify, playlist, single-song, archive, and runtime metrics
|
||||||
- Playlist History: existing playlist rows and delete completed/delete failed controls
|
- Playlist History: existing playlist rows and delete completed/delete failed controls
|
||||||
- Single Songs: existing single-track queue rows
|
- Single Songs: existing single-track queue rows
|
||||||
- Archive Browser: read-only browser-style listing of downloaded files inside the configured downloads root, with search, sort, file count, total size, and top-level folder grouping
|
- Archive Browser: read-only browser-style listing of downloaded files inside the configured downloads root, with search, sort, file count, total size, and top-level folder grouping
|
||||||
- Candidate Inspector: selected track details including Spotify URL, selected YouTube candidate, status, error, retry attempts, rejected candidates when available, and a Back/Clear selection action
|
- Candidate Inspector: selected track details including Spotify URL, selected YouTube candidate, candidate score, candidate reasoning, status, error classification, error summary, retry attempts, rejected candidates, and download diagnostics
|
||||||
|
|
||||||
The Archive destination field is frontend-persisted planning state. Runtime downloads still use the backend `DOWNLOADS_PATH` root. Per-run destination routing is pending backend support.
|
The Archive destination field is frontend-persisted planning state. Runtime downloads still use the backend `DOWNLOADS_PATH` root. Per-run destination routing is pending backend support.
|
||||||
|
|
||||||
@@ -93,7 +117,40 @@ The Archive Browser uses:
|
|||||||
|
|
||||||
It returns the configured downloads root and files under that root only. It does not allow arbitrary filesystem browsing, deletion, moving, renaming, or path traversal.
|
It returns the configured downloads root and files under that root only. It does not allow arbitrary filesystem browsing, deletion, moving, renaming, or path traversal.
|
||||||
|
|
||||||
Candidate alternatives are limited to the selected YouTube URL and rejected YouTube URLs already stored for the track. Full scoring history is not persisted yet.
|
Candidate Inspector displays persisted candidate provenance including:
|
||||||
|
|
||||||
|
- Selected candidate
|
||||||
|
- Candidate score
|
||||||
|
- Candidate reasoning
|
||||||
|
- Rejected candidates
|
||||||
|
- Download attempts
|
||||||
|
- Error classification
|
||||||
|
- Error summaries
|
||||||
|
|
||||||
|
Full candidate-pool history is not yet retained.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Operations Snapshot API
|
||||||
|
|
||||||
|
Spooty exposes deterministic operational truth through:
|
||||||
|
|
||||||
|
GET /api/operations/snapshot
|
||||||
|
|
||||||
|
This endpoint provides:
|
||||||
|
|
||||||
|
- Runtime operation state
|
||||||
|
- Queue depths
|
||||||
|
- Active searches
|
||||||
|
- Active downloads
|
||||||
|
- Playlist counts
|
||||||
|
- Track counts
|
||||||
|
- Failure summaries
|
||||||
|
- Candidate-selection metadata
|
||||||
|
|
||||||
|
The endpoint is intended for Workspace integrations and diagnostics surfaces.
|
||||||
|
|
||||||
|
Workspace consumes this truth rather than deriving operational state independently.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -119,7 +176,7 @@ Example:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Quick Start (Recommended)
|
# Quick Start Recommended
|
||||||
|
|
||||||
## 1. Create Spotify Developer App
|
## 1. Create Spotify Developer App
|
||||||
|
|
||||||
@@ -144,23 +201,21 @@ Copy:
|
|||||||
|
|
||||||
Create a secure env file:
|
Create a secure env file:
|
||||||
|
|
||||||
~~~bash
|
sudo mkdir -p /etc/tokens
|
||||||
sudo mkdir -p /etc/tokens
|
|
||||||
|
|
||||||
sudo tee /etc/tokens/spotify.env > /dev/null <<'EOT'
|
sudo tee /etc/tokens/spotify.env > /dev/null <<'EOT'
|
||||||
SPOTIFY_CLIENT_ID=your_client_id
|
SPOTIFY_CLIENT_ID=your_client_id
|
||||||
SPOTIFY_CLIENT_SECRET=your_client_secret
|
SPOTIFY_CLIENT_SECRET=your_client_secret
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
sudo chown root:$USER /etc/tokens/spotify.env
|
sudo chown root:$USER /etc/tokens/spotify.env
|
||||||
sudo chmod 640 /etc/tokens/spotify.env
|
sudo chmod 640 /etc/tokens/spotify.env
|
||||||
~~~
|
|
||||||
|
|
||||||
Never commit this file.
|
Never commit this file.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Export YouTube Cookies (Recommended)
|
## 3. Export YouTube Cookies Recommended
|
||||||
|
|
||||||
YouTube increasingly rate-limits or age-gates anonymous downloads.
|
YouTube increasingly rate-limits or age-gates anonymous downloads.
|
||||||
|
|
||||||
@@ -168,30 +223,26 @@ Export a Netscape-format `cookies.txt` from a logged-in browser session.
|
|||||||
|
|
||||||
Recommended storage:
|
Recommended storage:
|
||||||
|
|
||||||
~~~bash
|
sudo cp cookies.txt /etc/tokens/youtube.cookies.txt
|
||||||
sudo cp cookies.txt /etc/tokens/youtube.cookies.txt
|
sudo chown root:$USER /etc/tokens/youtube.cookies.txt
|
||||||
sudo chown root:$USER /etc/tokens/youtube.cookies.txt
|
sudo chmod 640 /etc/tokens/youtube.cookies.txt
|
||||||
sudo chmod 640 /etc/tokens/youtube.cookies.txt
|
|
||||||
~~~
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Docker Run
|
# Docker Run
|
||||||
|
|
||||||
~~~bash
|
docker run --rm -p 3000:3000 \
|
||||||
docker run --rm -p 3000:3000 \
|
--env-file /etc/tokens/spotify.env \
|
||||||
--env-file /etc/tokens/spotify.env \
|
-e SPOTIFY_REDIRECT_URI='http://127.0.0.1:3000/api/spotify/callback' \
|
||||||
-e SPOTIFY_REDIRECT_URI='http://127.0.0.1:3000/api/spotify/callback' \
|
-e AUTH_ENABLED=true \
|
||||||
-e AUTH_ENABLED=true \
|
-e SPOOTY_AUTH_TOKEN=change_this_token \
|
||||||
-e SPOOTY_AUTH_TOKEN=change_this_token \
|
-e YT_SEARCH_DELAY_MS=7000 \
|
||||||
-e YT_SEARCH_DELAY_MS=7000 \
|
-e YT_DOWNLOADS_PER_MINUTE=6 \
|
||||||
-e YT_DOWNLOADS_PER_MINUTE=6 \
|
-e YT_COOKIES_FILE=/spooty/config/youtube.cookies.txt \
|
||||||
-e YT_COOKIES_FILE=/spooty/config/youtube.cookies.txt \
|
-v "$PWD/downloads:/spooty/backend/downloads" \
|
||||||
-v "$PWD/downloads:/spooty/backend/downloads" \
|
-v "$PWD/spooty-config:/spooty/backend/config" \
|
||||||
-v "$PWD/spooty-config:/spooty/backend/config" \
|
-v "/etc/tokens/youtube.cookies.txt:/spooty/config/youtube.cookies.txt:ro" \
|
||||||
-v "/etc/tokens/youtube.cookies.txt:/spooty/config/youtube.cookies.txt:ro" \
|
jarri-spooty:local
|
||||||
jarri-spooty:local
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Open:
|
Open:
|
||||||
|
|
||||||
@@ -209,7 +260,7 @@ Then:
|
|||||||
|
|
||||||
# Deterministic YouTube Fallback Handling
|
# Deterministic YouTube Fallback Handling
|
||||||
|
|
||||||
The hardened branch now uses direct `yt-dlp` CLI execution rather than relying entirely on wrapper abstractions.
|
The hardened branch uses direct `yt-dlp` CLI execution rather than relying entirely on wrapper abstractions.
|
||||||
|
|
||||||
This provides:
|
This provides:
|
||||||
|
|
||||||
@@ -242,10 +293,8 @@ Aggressive YouTube access can trigger:
|
|||||||
|
|
||||||
Recommended safe pacing:
|
Recommended safe pacing:
|
||||||
|
|
||||||
~~~bash
|
-e YT_SEARCH_DELAY_MS=7000
|
||||||
-e YT_SEARCH_DELAY_MS=7000
|
-e YT_DOWNLOADS_PER_MINUTE=6
|
||||||
-e YT_DOWNLOADS_PER_MINUTE=6
|
|
||||||
~~~
|
|
||||||
|
|
||||||
The hardened branch also includes additional internal pacing and retry coordination to reduce:
|
The hardened branch also includes additional internal pacing and retry coordination to reduce:
|
||||||
|
|
||||||
@@ -269,15 +318,13 @@ Never commit:
|
|||||||
|
|
||||||
Recommended `.gitignore` additions:
|
Recommended `.gitignore` additions:
|
||||||
|
|
||||||
~~~gitignore
|
downloads/
|
||||||
downloads/
|
config/
|
||||||
config/
|
spooty-config/
|
||||||
spooty-config/
|
*.sqlite
|
||||||
*.sqlite
|
cookies.txt
|
||||||
cookies.txt
|
.env
|
||||||
.env
|
.env.local
|
||||||
.env.local
|
|
||||||
~~~
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Generated
+4
-4
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "spooty",
|
"name": "spooty",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "spooty",
|
"name": "spooty",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"src/backend",
|
"src/backend",
|
||||||
"src/frontend"
|
"src/frontend"
|
||||||
@@ -25508,7 +25508,7 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"src/backend": {
|
"src/backend": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bullmq": "^10.2.3",
|
"@nestjs/bullmq": "^10.2.3",
|
||||||
@@ -25571,7 +25571,7 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"src/frontend": {
|
"src/frontend": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^19.2.22",
|
"@angular/animations": "^19.2.22",
|
||||||
"@angular/common": "^19.2.22",
|
"@angular/common": "^19.2.22",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"name": "spooty",
|
"name": "spooty",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"src/backend",
|
"src/backend",
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ TOKEN="${SPOOTY_AUTH_TOKEN:-test-token}"
|
|||||||
DOWNLOADS="${DOWNLOADS:-$PROJECT/test-downloads}"
|
DOWNLOADS="${DOWNLOADS:-$PROJECT/test-downloads}"
|
||||||
CONFIG_DIR="${CONFIG_DIR:-$PROJECT/spooty-config}"
|
CONFIG_DIR="${CONFIG_DIR:-$PROJECT/spooty-config}"
|
||||||
ENV_FILE="${ENV_FILE:-/etc/tokens/spotify.env}"
|
ENV_FILE="${ENV_FILE:-/etc/tokens/spotify.env}"
|
||||||
|
|
||||||
YT_COOKIES_FILE_HOST="${YT_COOKIES_FILE_HOST:-/etc/tokens/youtube.cookies.txt}"
|
YT_COOKIES_FILE_HOST="${YT_COOKIES_FILE_HOST:-/etc/tokens/youtube.cookies.txt}"
|
||||||
|
RUNTIME_SECRETS_DIR="${RUNTIME_SECRETS_DIR:-$PROJECT/spooty-runtime-secrets}"
|
||||||
|
YT_COOKIES_FILE_STAGED="${YT_COOKIES_FILE_STAGED:-$RUNTIME_SECRETS_DIR/youtube.cookies.txt}"
|
||||||
YT_COOKIES_FILE_CONTAINER="${YT_COOKIES_FILE_CONTAINER:-/spooty/config/youtube.cookies.txt}"
|
YT_COOKIES_FILE_CONTAINER="${YT_COOKIES_FILE_CONTAINER:-/spooty/config/youtube.cookies.txt}"
|
||||||
|
|
||||||
cd "$PROJECT"
|
cd "$PROJECT"
|
||||||
@@ -28,6 +31,18 @@ if [[ "${1:-}" == "--build" ]]; then
|
|||||||
$DOCKER build -t "$IMAGE" .
|
$DOCKER build -t "$IMAGE" .
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "== Preparing runtime directories =="
|
||||||
|
mkdir -p "$DOWNLOADS" "$CONFIG_DIR" "$RUNTIME_SECRETS_DIR"
|
||||||
|
|
||||||
|
if [[ -f "$YT_COOKIES_FILE_HOST" ]]; then
|
||||||
|
echo "== Staging YouTube cookies =="
|
||||||
|
cp "$YT_COOKIES_FILE_HOST" "$YT_COOKIES_FILE_STAGED"
|
||||||
|
chmod 0666 "$YT_COOKIES_FILE_STAGED"
|
||||||
|
else
|
||||||
|
echo "ERROR: YouTube cookies file not found: $YT_COOKIES_FILE_HOST" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "== Removing named container if present =="
|
echo "== Removing named container if present =="
|
||||||
$DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
$DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
@@ -37,8 +52,6 @@ if [[ -n "$PORT_CONTAINERS" ]]; then
|
|||||||
$DOCKER rm -f $PORT_CONTAINERS
|
$DOCKER rm -f $PORT_CONTAINERS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$DOWNLOADS" "$CONFIG_DIR"
|
|
||||||
|
|
||||||
echo "== Starting Jarri Spooty =="
|
echo "== Starting Jarri Spooty =="
|
||||||
$DOCKER run -d \
|
$DOCKER run -d \
|
||||||
--name "$CONTAINER" \
|
--name "$CONTAINER" \
|
||||||
@@ -52,7 +65,7 @@ $DOCKER run -d \
|
|||||||
-e YT_DOWNLOAD_FALLBACK_ATTEMPTS=3 \
|
-e YT_DOWNLOAD_FALLBACK_ATTEMPTS=3 \
|
||||||
-e YT_COOKIES_FILE="$YT_COOKIES_FILE_CONTAINER" \
|
-e YT_COOKIES_FILE="$YT_COOKIES_FILE_CONTAINER" \
|
||||||
-v "$DOWNLOADS:/spooty/backend/downloads" \
|
-v "$DOWNLOADS:/spooty/backend/downloads" \
|
||||||
-v "$YT_COOKIES_FILE_HOST:$YT_COOKIES_FILE_CONTAINER:ro" \
|
-v "$YT_COOKIES_FILE_STAGED:$YT_COOKIES_FILE_CONTAINER" \
|
||||||
-v "$CONFIG_DIR:/spooty/backend/config" \
|
-v "$CONFIG_DIR:/spooty/backend/config" \
|
||||||
"$IMAGE"
|
"$IMAGE"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -200,7 +200,18 @@ export class TrackService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private rejectCurrentYoutubeCandidate(
|
private isPermanentYoutubeCandidateRejection(
|
||||||
|
reason: RejectionReason,
|
||||||
|
): boolean {
|
||||||
|
return [
|
||||||
|
'YOUTUBE_VIDEO_UNAVAILABLE',
|
||||||
|
'YOUTUBE_AGE_GATED',
|
||||||
|
'YOUTUBE_NO_FORMATS',
|
||||||
|
'YOUTUBE_PRIVATE_VIDEO',
|
||||||
|
].includes(reason.rejectionClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private recordCurrentYoutubeCandidateFailure(
|
||||||
track: TrackEntity,
|
track: TrackEntity,
|
||||||
reason: RejectionReason,
|
reason: RejectionReason,
|
||||||
): {
|
): {
|
||||||
@@ -212,7 +223,10 @@ export class TrackService {
|
|||||||
const rejectedCandidate = this.buildRejectedYoutubeCandidate(track, reason);
|
const rejectedCandidate = this.buildRejectedYoutubeCandidate(track, reason);
|
||||||
|
|
||||||
if (rejectedCandidate) {
|
if (rejectedCandidate) {
|
||||||
if (!rejectedUrls.includes(rejectedCandidate.url)) {
|
if (
|
||||||
|
this.isPermanentYoutubeCandidateRejection(reason) &&
|
||||||
|
!rejectedUrls.includes(rejectedCandidate.url)
|
||||||
|
) {
|
||||||
rejectedUrls.push(rejectedCandidate.url);
|
rejectedUrls.push(rejectedCandidate.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,11 +313,6 @@ export class TrackService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rejectedCandidateFields = this.rejectCurrentYoutubeCandidate(track, {
|
|
||||||
rejectionClass: 'MANUAL_RETRY',
|
|
||||||
rejectionSummary: 'Rejected by manual retry',
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.update(id, {
|
await this.update(id, {
|
||||||
...track,
|
...track,
|
||||||
youtubeUrl: null,
|
youtubeUrl: null,
|
||||||
@@ -311,7 +320,9 @@ export class TrackService {
|
|||||||
selectedYoutubeAuthor: null,
|
selectedYoutubeAuthor: null,
|
||||||
selectedYoutubeScore: null,
|
selectedYoutubeScore: null,
|
||||||
selectedYoutubeReason: null,
|
selectedYoutubeReason: null,
|
||||||
...rejectedCandidateFields,
|
rejectedYoutubeUrls: track.rejectedYoutubeUrls || JSON.stringify([]),
|
||||||
|
rejectedYoutubeCandidatesJson:
|
||||||
|
track.rejectedYoutubeCandidatesJson || JSON.stringify([]),
|
||||||
downloadAttemptCount: 0,
|
downloadAttemptCount: 0,
|
||||||
error: null,
|
error: null,
|
||||||
status: TrackStatusEnum.New,
|
status: TrackStatusEnum.New,
|
||||||
@@ -476,7 +487,7 @@ export class TrackService {
|
|||||||
const nextAttemptCount = (track.downloadAttemptCount || 0) + 1;
|
const nextAttemptCount = (track.downloadAttemptCount || 0) + 1;
|
||||||
const maxAttempts = this.getMaxDownloadAttempts();
|
const maxAttempts = this.getMaxDownloadAttempts();
|
||||||
const rejectedCandidateFields = error
|
const rejectedCandidateFields = error
|
||||||
? this.rejectCurrentYoutubeCandidate(track, this.classifyRejection(error))
|
? this.recordCurrentYoutubeCandidateFailure(track, this.classifyRejection(error))
|
||||||
: {
|
: {
|
||||||
rejectedYoutubeUrls: this.stringifyRejectedYoutubeUrls(
|
rejectedYoutubeUrls: this.stringifyRejectedYoutubeUrls(
|
||||||
this.parseRejectedYoutubeUrls(track),
|
this.parseRejectedYoutubeUrls(track),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --proxy-config proxy.conf.json",
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
|
|||||||
Reference in New Issue
Block a user