-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathremove_ghost_tracks.py
More file actions
128 lines (101 loc) · 4.23 KB
/
remove_ghost_tracks.py
File metadata and controls
128 lines (101 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
"""YouTube Music 유령곡(재생 불가 곡) 탐지 및 삭제 스크립트"""
from ytmusicapi import YTMusic
from ytmusicapi.auth.oauth.credentials import OAuthCredentials
import time
import json
import os
from dotenv import load_dotenv
# 환경 변수 로드
load_dotenv()
# 클라이언트 정보 (환경 변수에서 가져오기)
CLIENT_ID = os.getenv('YOUTUBE_CLIENT_ID')
CLIENT_SECRET = os.getenv('YOUTUBE_CLIENT_SECRET')
if not CLIENT_ID or not CLIENT_SECRET:
raise ValueError("환경 변수 YOUTUBE_CLIENT_ID와 YOUTUBE_CLIENT_SECRET를 설정해주세요. .env 파일을 확인하세요.")
def main():
print("🎵 YouTube Music 유령곡 삭제 도구\n")
# OAuth 인증으로 초기화
oauth_credentials = OAuthCredentials(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET
)
ytmusic = YTMusic('oauth.json', oauth_credentials=oauth_credentials)
print("좋아요 표시한 곡 불러오는 중...")
liked_songs = ytmusic.get_liked_songs(limit=5000)
tracks = liked_songs.get('tracks', [])
print(f"총 {len(tracks)}곡 발견. 유령곡 검사 중...\n")
ghost_tracks = []
normal_tracks = []
for i, track in enumerate(tracks, 1):
video_id = track.get('videoId')
title = track.get('title', '알 수 없음')
artists = track.get('artists')
artist = artists[0].get('name', '알 수 없음') if artists else '알 수 없음'
is_ghost = False
reason = ""
if not video_id:
is_ghost = True
reason = "videoId 없음"
else:
try:
song_info = ytmusic.get_song(video_id)
playability = song_info.get('playabilityStatus', {})
status = playability.get('status', '')
if status in ['UNPLAYABLE', 'ERROR', 'CONTENT_CHECK_REQUIRED']:
is_ghost = True
reason = f"상태: {status}"
elif status == 'LOGIN_REQUIRED':
# 로그인 필요는 유령이 아닐 수 있음
pass
except Exception as e:
is_ghost = True
reason = f"조회 실패: {str(e)[:50]}"
if is_ghost:
ghost_tracks.append(track)
print(f"👻 [{i}/{len(tracks)}] 유령곡: {title} - {artist} ({reason})")
else:
normal_tracks.append(track)
print(f"✅ [{i}/{len(tracks)}] 정상: {title} - {artist}")
time.sleep(0.3) # API 속도 제한 방지
print(f"\n{'='*50}")
print(f"📊 검사 결과: 정상 {len(normal_tracks)}곡 / 유령 {len(ghost_tracks)}곡")
print(f"{'='*50}\n")
if not ghost_tracks:
print("🎉 유령곡이 없습니다!")
return
print("발견된 유령곡 목록:")
for i, track in enumerate(ghost_tracks, 1):
title = track.get('title', '알 수 없음')
artists = track.get('artists')
artist = artists[0].get('name', '알 수 없음') if artists else '알 수 없음'
print(f" {i}. {title} - {artist}")
print()
confirm = input("❓ 유령곡을 삭제하시겠습니까? (y/n): ")
if confirm.lower() != 'y':
print("취소되었습니다.")
return
print("\n🗑️ 유령곡 삭제 중...\n")
success_count = 0
fail_count = 0
for track in ghost_tracks:
video_id = track.get('videoId')
title = track.get('title', '알 수 없음')
try:
if video_id:
# 좋아요 취소 = 좋아요 목록에서 제거
ytmusic.rate_song(video_id, 'INDIFFERENT')
print(f"✅ 삭제됨: {title}")
success_count += 1
else:
print(f"⚠️ videoId 없어서 삭제 불가: {title}")
fail_count += 1
time.sleep(0.5)
except Exception as e:
print(f"❌ 삭제 실패: {title} - {e}")
fail_count += 1
print(f"\n{'='*50}")
print(f"🎉 완료! 성공: {success_count}곡 / 실패: {fail_count}곡")
print(f"{'='*50}")
if __name__ == "__main__":
main()