mirror of
https://github.com/ReviveMii/riivivetube
synced 2025-09-02 19:41:07 +02:00
Compare commits
5 Commits
b41e6dd542
...
898eba358c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
898eba358c | ||
![]() |
cdf7f6f2b7 | ||
![]() |
dff7bba236 | ||
![]() |
6eaf1f6e16 | ||
![]() |
d83733fb10 |
BIN
apiplayer.swf
BIN
apiplayer.swf
Binary file not shown.
141
riivivetube.py
141
riivivetube.py
@ -151,6 +151,61 @@ def get_video_info():
|
||||
video_info = GetVideoInfo().build(video_id)
|
||||
return video_info
|
||||
|
||||
@app.route('/feeds/api/videos/<video_id>')
|
||||
def video_details(video_id):
|
||||
try:
|
||||
video_info = youtubei.get_video_info(video_id)
|
||||
if not video_info:
|
||||
return Response(
|
||||
'<error>Video not found</error>',
|
||||
mimetype='text/xml',
|
||||
status=404
|
||||
)
|
||||
|
||||
ns = {
|
||||
'media': 'http://search.yahoo.com/mrss/',
|
||||
'yt': 'http://www.youtube.com/xml/schemas/2015'
|
||||
}
|
||||
|
||||
root = ET.Element('entry')
|
||||
ET.SubElement(root, 'id').text = f"http://127.0.0.1:5005/feeds/api/videos/{video_id}"
|
||||
ET.SubElement(root, 'title').text = video_info.get('title', '')
|
||||
ET.SubElement(root, 'published').text = video_info.get('publishedText', '')
|
||||
author = ET.SubElement(root, 'author')
|
||||
ET.SubElement(author, 'name').text = video_info.get('author', '')
|
||||
media_group = ET.SubElement(root, 'media:group')
|
||||
ET.SubElement(
|
||||
media_group,
|
||||
'media:thumbnail',
|
||||
attrib={
|
||||
'yt:name': 'hqdefault',
|
||||
'url': f"http://i.ytimg.com/vi/{video_id}/hqdefault.jpg",
|
||||
'width': '320',
|
||||
'height': '240'
|
||||
}
|
||||
)
|
||||
ET.SubElement(
|
||||
media_group,
|
||||
'yt:duration',
|
||||
attrib={'seconds': str(video_info.get('lengthSeconds', 0))}
|
||||
)
|
||||
ET.SubElement(media_group, 'yt:videoid').text = video_id
|
||||
stats = ET.SubElement(root, 'yt:statistics')
|
||||
stats.set('viewCount', str(video_info.get('viewCount', 0)))
|
||||
stats.set('likeCount', str(video_info.get('likeCount', 0)))
|
||||
xml_str = ET.tostring(root, encoding='utf-8', method='xml').decode()
|
||||
xml_str = f'<?xml version="1.0" encoding="UTF-8"?>\n{xml_str}'
|
||||
|
||||
return Response(xml_str, mimetype='text/xml')
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
f'<error>{str(e)}</error>',
|
||||
mimetype='text/xml',
|
||||
status=500
|
||||
)
|
||||
|
||||
|
||||
@app.route("/wiitv")
|
||||
def wiitv():
|
||||
return send_from_directory(".", "leanbacklite_wii.swf", mimetype='application/x-shockwave-flash')
|
||||
@ -204,6 +259,92 @@ def completesearch():
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"Error processing suggestions: {str(e)}"}), 500
|
||||
|
||||
CONTENT_LENGTH = 50000000
|
||||
|
||||
@app.route('/git_video', methods=['GET'])
|
||||
def git_video():
|
||||
video_id = request.args.get('video_id')
|
||||
if not video_id:
|
||||
return "", 400
|
||||
|
||||
ytdlp_cmd = [
|
||||
'yt-dlp',
|
||||
f'https://www.youtube.com/watch?v={video_id}',
|
||||
'-f', '5/18/best[ext=mp4]/best[height<=240]',
|
||||
'--cookies', 'c.txt',
|
||||
'-g'
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(ytdlp_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return f"yt-dlp error: {result.stderr}", 500
|
||||
video_url = result.stdout.strip()
|
||||
if not video_url:
|
||||
return "No video URL found", 500
|
||||
except Exception as e:
|
||||
return f"yt-dlp error: {e}", 500
|
||||
|
||||
range_header = request.environ.get('HTTP_RANGE', '')
|
||||
range_start = 0
|
||||
range_end = CONTENT_LENGTH - 1
|
||||
status = '200 OK'
|
||||
headers = [
|
||||
('Content-Type', 'video/x-flv'),
|
||||
('Content-Disposition', f'attachment; filename="{video_id}.flv"'),
|
||||
('Accept-Ranges', 'bytes'),
|
||||
('Content-Length', str(CONTENT_LENGTH))
|
||||
]
|
||||
|
||||
if range_header:
|
||||
match = re.match(r'bytes=(\d+)-(\d*)', range_header)
|
||||
if match:
|
||||
range_start = int(match.group(1))
|
||||
range_end = int(match.group(2)) if match.group(2) else CONTENT_LENGTH - 1
|
||||
if range_start >= CONTENT_LENGTH or range_end >= CONTENT_LENGTH:
|
||||
return "Range Not Satisfiable", 416
|
||||
status = '206 Partial Content'
|
||||
headers = [
|
||||
('Content-Type', 'video/x-flv'),
|
||||
('Content-Disposition', f'attachment; filename="{video_id}.flv"'),
|
||||
('Accept-Ranges', 'bytes'),
|
||||
('Content-Range', f'bytes {range_start}-{range_end}/{CONTENT_LENGTH}'),
|
||||
('Content-Length', str(range_end - range_start + 1))
|
||||
]
|
||||
|
||||
total_bitrate = 500000 + 96000
|
||||
bytes_per_second = total_bitrate / 8
|
||||
start_time = range_start / bytes_per_second
|
||||
duration = (range_end - range_start + 1) / bytes_per_second
|
||||
|
||||
ffmpeg_cmd = [
|
||||
'ffmpeg', '-i', video_url,
|
||||
'-ss', str(start_time),
|
||||
'-t', str(duration),
|
||||
'-c:v', 'flv1', '-b:v', '500k', '-vf', 'scale=-1:240',
|
||||
'-c:a', 'mp3', '-b:a', '96k',
|
||||
'-r', '24', '-g', '24',
|
||||
'-f', 'flv', 'pipe:1'
|
||||
]
|
||||
|
||||
def generate(environ, start_response):
|
||||
start_response(status, headers)
|
||||
try:
|
||||
process = subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
while True:
|
||||
chunk = process.stdout.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
|
||||
stderr = process.communicate()[1]
|
||||
if process.returncode != 0:
|
||||
yield f"ffmpeg error: {stderr.decode()}".encode()
|
||||
except Exception as e:
|
||||
yield f"ffmpeg error: {str(e)}".encode()
|
||||
|
||||
return generate
|
||||
|
||||
|
||||
@app.route('/get_video', methods=['GET'])
|
||||
def get_video():
|
||||
|
63
youtubei.py
63
youtubei.py
@ -3,7 +3,7 @@
|
||||
|
||||
import requests
|
||||
|
||||
YOUTUBE_INNERTUBE_API_KEY = "test"
|
||||
YOUTUBE_INNERTUBE_API_KEY = "key"
|
||||
YOUTUBE_API_URL = f"https://www.youtube.com/youtubei/v1/search?key={YOUTUBE_INNERTUBE_API_KEY}®ion=US"
|
||||
YOUTUBE_API_BROWSE_URL = f"https://www.youtube.com/youtubei/v1/browse?key={YOUTUBE_INNERTUBE_API_KEY}"
|
||||
|
||||
@ -28,6 +28,65 @@ def safe_int(value, default=0):
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def get_video_info(video_id):
|
||||
url = "https://www.youtube.com/youtubei/v1/player"
|
||||
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Content-Type": "application/json",
|
||||
"X-YouTube-Client-Name": "1",
|
||||
"X-YouTube-Client-Version": "2.20231221"
|
||||
}
|
||||
|
||||
payload = {
|
||||
"context": {
|
||||
"client": {
|
||||
"hl": "en",
|
||||
"gl": "US",
|
||||
"clientName": "WEB",
|
||||
"clientVersion": "2.20231221"
|
||||
}
|
||||
},
|
||||
"videoId": video_id
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=payload, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
|
||||
video_details = data.get('videoDetails', {})
|
||||
microformat = data.get('microformat', {}).get('playerMicroformatRenderer', {})
|
||||
engagement = data.get('engagement', {})
|
||||
|
||||
|
||||
result = {
|
||||
'videoId': video_id,
|
||||
'title': video_details.get('title', ''),
|
||||
'author': video_details.get('author', ''),
|
||||
'authorId': video_details.get('channelId', ''),
|
||||
'lengthSeconds': video_details.get('lengthSeconds', 0),
|
||||
'viewCount': video_details.get('viewCount', 0),
|
||||
'description': video_details.get('shortDescription', ''),
|
||||
'publishedText': microformat.get('publishDate', ''),
|
||||
'averageRating': engagement.get('averageRating', 0),
|
||||
'likeCount': engagement.get('likeCount', 0),
|
||||
'dislikeCount': engagement.get('dislikeCount', 0),
|
||||
'keywords': video_details.get('keywords', []),
|
||||
'isLive': video_details.get('isLive', False),
|
||||
'thumbnail': {
|
||||
'thumbnails': video_details.get('thumbnail', {}).get('thumbnails', [])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching video info: {str(e)}")
|
||||
return None
|
||||
|
||||
def extract_length_text_and_seconds(vd):
|
||||
length_obj = vd.get("lengthText", {})
|
||||
simple_text = length_obj.get("simpleText")
|
||||
@ -239,5 +298,5 @@ def innertube_trending(trending_type=None, region="US", max_results=50):
|
||||
return [parse_video(v) for v in videos]
|
||||
|
||||
except Exception as e:
|
||||
print("error:", e)
|
||||
print("Trending Parsing Error:", e)
|
||||
return []
|
||||
|
Loading…
x
Reference in New Issue
Block a user