themehmi commited on
Commit
d403c94
·
verified ·
1 Parent(s): 8bde276

Upload 12 files

Browse files
app.py CHANGED
@@ -36,6 +36,7 @@ from ytmusicapi import YTMusic
36
  from curl_cffi import requests as cffi_requests
37
  import requests
38
  import socket
 
39
  import urllib3.util.connection as urllib3_cn
40
 
41
  def allowed_gai_family():
@@ -305,6 +306,26 @@ def api_oauth_poll():
305
  def api_status():
306
  return jsonify(system_status)
307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  @app.route('/api/frame', methods=['POST'])
309
  def api_frame():
310
  global start_closed_time, last_announced_state, smoothed_ear
@@ -540,12 +561,14 @@ def api_voice():
540
  user = get_db().users.find_one({'_id': ObjectId(user_id)})
541
 
542
  has_oauth = False
 
 
543
  try:
544
  if user and user.get('oauth_token'):
545
  filepath = f"oauth_{user_id}.json"
546
  with open(filepath, 'w') as f:
547
  f.write(user['oauth_token'])
548
- ytmusic = YTMusic(filepath)
549
  has_oauth = True
550
  # Read back in case of refresh
551
  with open(filepath, 'r') as f:
@@ -553,9 +576,9 @@ def api_voice():
553
  if refreshed != user['oauth_token']:
554
  get_db().users.update_one({'_id': ObjectId(user_id)}, {'$set': {'oauth_token': refreshed}})
555
  else:
556
- ytmusic = YTMusic()
557
  except Exception:
558
- ytmusic = YTMusic()
559
 
560
  is_personal = any(phrase in song.lower() for phrase in ["my liked", "my playlist", "my mix", "my supermix"])
561
 
 
36
  from curl_cffi import requests as cffi_requests
37
  import requests
38
  import socket
39
+ from pytubefix import YouTube
40
  import urllib3.util.connection as urllib3_cn
41
 
42
  def allowed_gai_family():
 
306
  def api_status():
307
  return jsonify(system_status)
308
 
309
+ @app.route('/api/music_url', methods=['GET'])
310
+ def api_music_url():
311
+ video_id = request.args.get('video_id')
312
+ if not video_id:
313
+ return jsonify({"error": "No video_id provided"}), 400
314
+ try:
315
+ url = f"https://www.youtube.com/watch?v={video_id}"
316
+ yt = YouTube(url, use_oauth=False, allow_oauth_cache=False)
317
+ audio_stream = yt.streams.get_audio_only()
318
+
319
+ return jsonify({
320
+ "url": audio_stream.url,
321
+ "title": yt.title,
322
+ "artist": yt.author,
323
+ "thumbnail": yt.thumbnail_url
324
+ })
325
+ except Exception as e:
326
+ print(f"[pytubefix error] {repr(e)}")
327
+ return jsonify({"error": str(e)}), 500
328
+
329
  @app.route('/api/frame', methods=['POST'])
330
  def api_frame():
331
  global start_closed_time, last_announced_state, smoothed_ear
 
561
  user = get_db().users.find_one({'_id': ObjectId(user_id)})
562
 
563
  has_oauth = False
564
+ # Inject a Chrome-impersonated session to bypass YouTube bot blocking on Hugging Face Spaces
565
+ custom_session = cffi_requests.Session(impersonate="chrome")
566
  try:
567
  if user and user.get('oauth_token'):
568
  filepath = f"oauth_{user_id}.json"
569
  with open(filepath, 'w') as f:
570
  f.write(user['oauth_token'])
571
+ ytmusic = YTMusic(filepath, requests_session=custom_session)
572
  has_oauth = True
573
  # Read back in case of refresh
574
  with open(filepath, 'r') as f:
 
576
  if refreshed != user['oauth_token']:
577
  get_db().users.update_one({'_id': ObjectId(user_id)}, {'$set': {'oauth_token': refreshed}})
578
  else:
579
+ ytmusic = YTMusic(requests_session=custom_session)
580
  except Exception:
581
+ ytmusic = YTMusic(requests_session=custom_session)
582
 
583
  is_personal = any(phrase in song.lower() for phrase in ["my liked", "my playlist", "my mix", "my supermix"])
584
 
oauth_6a3a2795fb802155791e0630.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"access_token": "ya29.a0AT3oNZ-PuKzIRRXTb7bPX0SN0KYTcIu9lC__Wzknc4vgasQRkpIZiXGbgTOZICSwWlSoDm2P5yKY6O3j57SH9dy1ia0Km_I0QpjZo9hXm87VY0RITmBH9kSdjGke-q7Pup-dkA-Kac8mjR6Zfjvrp1RSJOypgDfN9OIezfzUH5lUOfvh8qXPA3Cga_0UBU9AHs4HfJMaCgYKAQkSARMSFQHGX2MiG9DepMZw2wSjU4GYDs3iqw0206", "expires_in": 3599, "refresh_token": "1//05IRxCRE98oyGCgYIARAAGAUSNwF-L9IrrajZ_3yapvGMF8TTsZ0rCuItI83YsyBp3o6ane8mMDxZopFh8FpuWNSKfDN-xtEscRI", "scope": "https://www.googleapis.com/auth/youtube", "token_type": "Bearer", "refresh_token_expires_in": 604799, "client_id": "491487047585-lho8et7iouct8o0r4f7hd6djlgnrroff.apps.googleusercontent.com", "client_secret": "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"}
requirements.txt CHANGED
@@ -11,3 +11,4 @@ httpx<0.28.0
11
  ytmusicapi
12
  pymongo
13
  requests
 
 
11
  ytmusicapi
12
  pymongo
13
  requests
14
+ pytubefix
templates/index.html CHANGED
@@ -793,7 +793,6 @@
793
  }
794
  }
795
  </style>
796
- <script src="https://www.youtube.com/iframe_api"></script>
797
  </head>
798
 
799
  <body>
@@ -885,8 +884,8 @@
885
  <canvas id="canvasElement" style="display:none;"></canvas>
886
  </div>
887
 
888
- <!-- YOUTUBE IFRAME PLAYER (Hidden) -->
889
- <div id="youtube-player" style="display:none;"></div>
890
  </main>
891
 
892
  <!-- Right Sidebar for Copilot -->
@@ -1062,94 +1061,103 @@
1062
  let recognition = null;
1063
 
1064
 
1065
- // --- YOUTUBE IFRAME API PLAYER ---
1066
- let ytPlayer = null;
1067
  let isMmpPlaying = false;
1068
- let currentQueue = [];
1069
- let currentQueueIndex = 0;
1070
 
1071
- function onYouTubeIframeAPIReady() {
1072
- ytPlayer = new YT.Player('youtube-player', {
1073
- height: '0',
1074
- width: '0',
1075
- videoId: '',
1076
- playerVars: {
1077
- 'autoplay': 1,
1078
- 'controls': 0,
1079
- 'disablekb': 1,
1080
- 'fs': 0,
1081
- 'modestbranding': 1,
1082
- 'rel': 0
1083
- },
1084
- events: {
1085
- 'onReady': onPlayerReady,
1086
- 'onStateChange': onPlayerStateChange,
1087
- 'onError': onPlayerError
1088
- }
1089
- });
1090
- }
1091
 
1092
- function onPlayerReady(event) {
1093
- console.log("YouTube Player Ready");
1094
- }
 
1095
 
1096
- function onPlayerStateChange(event) {
1097
- if (event.data == YT.PlayerState.PLAYING) {
1098
- document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-pause"></i>';
1099
- isMmpPlaying = true;
1100
-
1101
- // Try to get actual video title
1102
- if (ytPlayer && ytPlayer.getVideoData) {
1103
- const data = ytPlayer.getVideoData();
1104
- if (data && data.title) {
1105
- document.getElementById('mmp-title').innerText = data.title;
1106
- }
1107
- }
1108
- } else if (event.data == YT.PlayerState.PAUSED) {
1109
- document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1110
- isMmpPlaying = false;
1111
- } else if (event.data == YT.PlayerState.BUFFERING) {
1112
- document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
1113
- isMmpPlaying = false;
1114
- } else if (event.data == YT.PlayerState.ENDED) {
1115
- document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1116
- isMmpPlaying = false;
1117
- if (currentQueue.length > currentQueueIndex + 1) {
1118
- currentQueueIndex++;
1119
- playNativeAudio(currentQueue[currentQueueIndex], "Up Next...");
1120
- } else {
1121
- document.getElementById('custom-media-player').style.display = 'none';
1122
- }
1123
  }
1124
- }
1125
 
1126
- function onPlayerError(event) {
1127
- console.error("YouTube Player Error:", event.data);
1128
  document.getElementById('mmp-title').innerText = "Playback Failed";
1129
  document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1130
  appendMessage('assistant', "Failed to load audio stream. Please try another song.");
1131
  isMmpPlaying = false;
1132
- }
 
 
 
 
1133
 
1134
  function toggleMmpPlay() {
1135
- if (!ytPlayer) return;
1136
  if (isMmpPlaying) {
1137
- ytPlayer.pauseVideo();
1138
  } else {
1139
- ytPlayer.playVideo();
 
 
1140
  }
1141
  }
1142
 
1143
- function playNativeAudio(videoId, titleGuess) {
1144
- if (!ytPlayer) return;
 
1145
 
1146
  document.getElementById('custom-media-player').style.display = 'flex';
1147
  document.getElementById('mmp-thumbnail').src = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
1148
- document.getElementById('mmp-title').innerText = "Loading...";
1149
  document.getElementById('mmp-artist').innerText = titleGuess || "YouTube Music";
1150
  document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
1151
 
1152
- ytPlayer.loadVideoById(videoId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1153
  }
1154
 
1155
  // Pre-load voices on page load
@@ -1167,8 +1175,8 @@
1167
  if (!text) return;
1168
 
1169
  // lower music volume
1170
- if (isMmpPlaying && ytPlayer) {
1171
- ytPlayer.setVolume(20);
1172
  }
1173
 
1174
  // Filter out markdown
@@ -1188,13 +1196,13 @@
1188
  utterance.onend = () => {
1189
  isSpeaking = false;
1190
  // restore music volume
1191
- if (isMmpPlaying && ytPlayer) {
1192
- ytPlayer.setVolume(100);
1193
  }
1194
  };
1195
  utterance.onerror = () => {
1196
  isSpeaking = false;
1197
- if (isMmpPlaying && ytPlayer) ytPlayer.setVolume(100);
1198
  };
1199
 
1200
  window.speechSynthesis.speak(utterance);
@@ -1234,11 +1242,11 @@
1234
  currentQueueIndex = 0;
1235
  playNativeAudio(currentQueue[currentQueueIndex], data.title);
1236
  } else if (data.action === "pause_native") {
1237
- if (ytPlayer) ytPlayer.pauseVideo();
1238
  } else if (data.action === "resume_native") {
1239
- if (ytPlayer) ytPlayer.playVideo();
1240
  } else if (data.action === "stop_native") {
1241
- if (ytPlayer) ytPlayer.stopVideo();
1242
  document.getElementById('custom-media-player').style.display = 'none';
1243
  } else if (data.action === "next_native") {
1244
  if (currentQueue.length > currentQueueIndex + 1) {
@@ -1255,19 +1263,15 @@
1255
  appendMessage('assistant', "No previous song in queue.");
1256
  }
1257
  } else if (data.action === "vol_up_native") {
1258
- if (ytPlayer) {
1259
- let vol = ytPlayer.getVolume();
1260
- ytPlayer.setVolume(Math.min(100, vol + 20));
1261
- }
1262
  } else if (data.action === "vol_down_native") {
1263
- if (ytPlayer) {
1264
- let vol = ytPlayer.getVolume();
1265
- ytPlayer.setVolume(Math.max(0, vol - 20));
1266
- }
1267
  } else if (data.action === "mute_native") {
1268
- if (ytPlayer) ytPlayer.mute();
1269
  } else if (data.action === "unmute_native") {
1270
- if (ytPlayer) ytPlayer.unMute();
1271
  } else if (data.action === "open_url" && data.url) {
1272
  let newWin = window.open(data.url, "_blank");
1273
  if (!newWin || newWin.closed || typeof newWin.closed == 'undefined') {
 
793
  }
794
  }
795
  </style>
 
796
  </head>
797
 
798
  <body>
 
884
  <canvas id="canvasElement" style="display:none;"></canvas>
885
  </div>
886
 
887
+ <!-- NATIVE AUDIO PLAYER (Using Piped API) -->
888
+ <audio id="nativeAudio" style="display:none;"></audio>
889
  </main>
890
 
891
  <!-- Right Sidebar for Copilot -->
 
1061
  let recognition = null;
1062
 
1063
 
1064
+ // --- NATIVE AUDIO PLAYER (Using Piped API) ---
1065
+ const nativeAudio = document.getElementById('nativeAudio') || new Audio();
1066
  let isMmpPlaying = false;
 
 
1067
 
1068
+ nativeAudio.addEventListener('play', () => {
1069
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-pause"></i>';
1070
+ isMmpPlaying = true;
1071
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
 
1073
+ nativeAudio.addEventListener('playing', () => {
1074
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-pause"></i>';
1075
+ isMmpPlaying = true;
1076
+ });
1077
 
1078
+ nativeAudio.addEventListener('pause', () => {
1079
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1080
+ isMmpPlaying = false;
1081
+ });
1082
+
1083
+ nativeAudio.addEventListener('waiting', () => {
1084
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
1085
+ isMmpPlaying = false;
1086
+ });
1087
+
1088
+ nativeAudio.addEventListener('ended', () => {
1089
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1090
+ isMmpPlaying = false;
1091
+ if (currentQueue.length > currentQueueIndex + 1) {
1092
+ currentQueueIndex++;
1093
+ playNativeAudio(currentQueue[currentQueueIndex], "Up Next...");
1094
+ } else {
1095
+ document.getElementById('custom-media-player').style.display = 'none';
 
 
 
 
 
 
 
 
 
1096
  }
1097
+ });
1098
 
1099
+ nativeAudio.addEventListener('error', (e) => {
1100
+ console.error("Native Audio Player Error:", e);
1101
  document.getElementById('mmp-title').innerText = "Playback Failed";
1102
  document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1103
  appendMessage('assistant', "Failed to load audio stream. Please try another song.");
1104
  isMmpPlaying = false;
1105
+ });
1106
+
1107
+ let currentQueue = [];
1108
+ let currentQueueIndex = 0;
1109
+ let currentExtractionId = 0;
1110
 
1111
  function toggleMmpPlay() {
 
1112
  if (isMmpPlaying) {
1113
+ nativeAudio.pause();
1114
  } else {
1115
+ if (nativeAudio.src) {
1116
+ nativeAudio.play().catch(e => console.error("Play error:", e));
1117
+ }
1118
  }
1119
  }
1120
 
1121
+ async function playNativeAudio(videoId, titleGuess) {
1122
+ const extractionId = ++currentExtractionId;
1123
+ nativeAudio.pause();
1124
 
1125
  document.getElementById('custom-media-player').style.display = 'flex';
1126
  document.getElementById('mmp-thumbnail').src = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
1127
+ document.getElementById('mmp-title').innerText = "Extracting Audio Stream...";
1128
  document.getElementById('mmp-artist').innerText = titleGuess || "YouTube Music";
1129
  document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
1130
 
1131
+ try {
1132
+ // Fetch direct audio stream URL from backend
1133
+ const response = await fetch(`/api/music_url?video_id=${videoId}`);
1134
+ const data = await response.json();
1135
+
1136
+ if (extractionId !== currentExtractionId) return; // Superceded by another play
1137
+
1138
+ if (data.url) {
1139
+ document.getElementById('mmp-title').innerText = data.title || titleGuess;
1140
+ document.getElementById('mmp-artist').innerText = data.artist || "YouTube Music";
1141
+
1142
+ nativeAudio.src = data.url;
1143
+ nativeAudio.volume = 1.0;
1144
+ nativeAudio.play().catch(e => {
1145
+ console.error("Autoplay prevented:", e);
1146
+ document.getElementById('mmp-title').innerText = data.title || titleGuess;
1147
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1148
+ appendMessage('assistant', "Please click the play button manually to start the song.");
1149
+ });
1150
+ } else {
1151
+ throw new Error(data.error || "No URL returned");
1152
+ }
1153
+ } catch (err) {
1154
+ console.error("Extraction error:", err);
1155
+ if (extractionId === currentExtractionId) {
1156
+ document.getElementById('mmp-title').innerText = "Extraction Failed";
1157
+ document.getElementById('mmp-play-pause').innerHTML = '<i class="fa-solid fa-play"></i>';
1158
+ appendMessage('assistant', "Failed to extract audio stream for this song. Please try another one.");
1159
+ }
1160
+ }
1161
  }
1162
 
1163
  // Pre-load voices on page load
 
1175
  if (!text) return;
1176
 
1177
  // lower music volume
1178
+ if (isMmpPlaying) {
1179
+ nativeAudio.volume = 0.2;
1180
  }
1181
 
1182
  // Filter out markdown
 
1196
  utterance.onend = () => {
1197
  isSpeaking = false;
1198
  // restore music volume
1199
+ if (isMmpPlaying) {
1200
+ nativeAudio.volume = 1.0;
1201
  }
1202
  };
1203
  utterance.onerror = () => {
1204
  isSpeaking = false;
1205
+ if (isMmpPlaying) nativeAudio.volume = 1.0;
1206
  };
1207
 
1208
  window.speechSynthesis.speak(utterance);
 
1242
  currentQueueIndex = 0;
1243
  playNativeAudio(currentQueue[currentQueueIndex], data.title);
1244
  } else if (data.action === "pause_native") {
1245
+ nativeAudio.pause();
1246
  } else if (data.action === "resume_native") {
1247
+ nativeAudio.play();
1248
  } else if (data.action === "stop_native") {
1249
+ nativeAudio.pause();
1250
  document.getElementById('custom-media-player').style.display = 'none';
1251
  } else if (data.action === "next_native") {
1252
  if (currentQueue.length > currentQueueIndex + 1) {
 
1263
  appendMessage('assistant', "No previous song in queue.");
1264
  }
1265
  } else if (data.action === "vol_up_native") {
1266
+ let vol = nativeAudio.volume;
1267
+ nativeAudio.volume = Math.min(1.0, vol + 0.2);
 
 
1268
  } else if (data.action === "vol_down_native") {
1269
+ let vol = nativeAudio.volume;
1270
+ nativeAudio.volume = Math.max(0.0, vol - 0.2);
 
 
1271
  } else if (data.action === "mute_native") {
1272
+ nativeAudio.muted = true;
1273
  } else if (data.action === "unmute_native") {
1274
+ nativeAudio.muted = false;
1275
  } else if (data.action === "open_url" && data.url) {
1276
  let newWin = window.open(data.url, "_blank");
1277
  if (!newWin || newWin.closed || typeof newWin.closed == 'undefined') {