diff --git a/run_me.sh b/run_me.sh index 30dbafd..bdca6cc 100755 --- a/run_me.sh +++ b/run_me.sh @@ -9,7 +9,10 @@ TOKEN="${SPOOTY_AUTH_TOKEN:-test-token}" DOWNLOADS="${DOWNLOADS:-$PROJECT/test-downloads}" CONFIG_DIR="${CONFIG_DIR:-$PROJECT/spooty-config}" ENV_FILE="${ENV_FILE:-/etc/tokens/spotify.env}" + 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}" cd "$PROJECT" @@ -28,6 +31,18 @@ if [[ "${1:-}" == "--build" ]]; then $DOCKER build -t "$IMAGE" . 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 ==" $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || true @@ -37,8 +52,6 @@ if [[ -n "$PORT_CONTAINERS" ]]; then $DOCKER rm -f $PORT_CONTAINERS fi -mkdir -p "$DOWNLOADS" "$CONFIG_DIR" - echo "== Starting Jarri Spooty ==" $DOCKER run -d \ --name "$CONTAINER" \ @@ -52,7 +65,7 @@ $DOCKER run -d \ -e YT_DOWNLOAD_FALLBACK_ATTEMPTS=3 \ -e YT_COOKIES_FILE="$YT_COOKIES_FILE_CONTAINER" \ -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" \ "$IMAGE" diff --git a/src/backend/src/track/track.service.ts b/src/backend/src/track/track.service.ts index 8a939a1..022759a 100644 --- a/src/backend/src/track/track.service.ts +++ b/src/backend/src/track/track.service.ts @@ -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, reason: RejectionReason, ): { @@ -212,7 +223,10 @@ export class TrackService { const rejectedCandidate = this.buildRejectedYoutubeCandidate(track, reason); if (rejectedCandidate) { - if (!rejectedUrls.includes(rejectedCandidate.url)) { + if ( + this.isPermanentYoutubeCandidateRejection(reason) && + !rejectedUrls.includes(rejectedCandidate.url) + ) { rejectedUrls.push(rejectedCandidate.url); } @@ -299,11 +313,6 @@ export class TrackService { return; } - const rejectedCandidateFields = this.rejectCurrentYoutubeCandidate(track, { - rejectionClass: 'MANUAL_RETRY', - rejectionSummary: 'Rejected by manual retry', - }); - await this.update(id, { ...track, youtubeUrl: null, @@ -311,7 +320,9 @@ export class TrackService { selectedYoutubeAuthor: null, selectedYoutubeScore: null, selectedYoutubeReason: null, - ...rejectedCandidateFields, + rejectedYoutubeUrls: track.rejectedYoutubeUrls || JSON.stringify([]), + rejectedYoutubeCandidatesJson: + track.rejectedYoutubeCandidatesJson || JSON.stringify([]), downloadAttemptCount: 0, error: null, status: TrackStatusEnum.New, @@ -476,7 +487,7 @@ export class TrackService { const nextAttemptCount = (track.downloadAttemptCount || 0) + 1; const maxAttempts = this.getMaxDownloadAttempts(); const rejectedCandidateFields = error - ? this.rejectCurrentYoutubeCandidate(track, this.classifyRejection(error)) + ? this.recordCurrentYoutubeCandidateFailure(track, this.classifyRejection(error)) : { rejectedYoutubeUrls: this.stringifyRejectedYoutubeUrls( this.parseRejectedYoutubeUrls(track),