aboutsummaryrefslogtreecommitdiffstats
path: root/yt_radio.py
blob: 3897899c5b71b77cbdcfb5a196394c080bd78eac (plain) (blame)
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
from flask import Flask, Response, stream_with_context, abort
from yt_dlp import YoutubeDL
import subprocess
import json
import threading
import random
import os
import sys
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)

PLAYLIST = [
]

BASE_URL = os.environ.get("BASE_URL")
RANDOMIZE_PLAYLIST = os.environ.get("RANDOMIZE_PLAYLIST")
METADATA = {}

def convert_playlist_to_links(link: str):
    ydl_opts = {
        "quiet": True,
        "extract_flat": True,   # don't download, just metadata
    }
    urls = []
    with YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(link, download=False)
        for entry in info["entries"]:
            urls.append(f"https://www.youtube.com/watch?v={entry['id']}")
    if bool(RANDOMIZE_PLAYLIST) == True:
        random.shuffle(urls)
    return urls

def fetch_metadata(index, url):
    try:
        result = subprocess.run(
            ["yt-dlp", "--dump-json", url],
            capture_output=True,
            text=True,
            timeout=20
        )
        data = json.loads(result.stdout)
        METADATA[index] = {
            "title": data.get("title", f"Track {index+1}"),
            "artist": data.get("uploader", "Unknown"),
            "duration": data.get("duration", -1)
        }
    except Exception:
        METADATA[index] = {
            "title": f"Track {index+1}",
            "artist": "Unknown",
            "duration": -1
        }


for i, url in enumerate(PLAYLIST):
    threading.Thread(target=fetch_metadata, args=(i, url), daemon=True).start()


@app.route("/playlist.m3u")
def playlist():
    lines = ["#EXTM3U"]

    indices = list(range(len(PLAYLIST)))

    for i in indices:
        meta = METADATA.get(i, {
            "title": f"Track {i+1}",
            "artist": "Unknown",
            "duration": -1
        })

        lines.append(
            f"#EXTINF:{meta['duration']},{meta['artist']} - {meta['title']}"
        )
        lines.append(f"{BASE_URL}/track/{i}")

    return Response("\n".join(lines),
                    mimetype="audio/x-mpegurl")

@app.route("/track/<int:index>")
def track(index):
    if index < 0 or index >= len(PLAYLIST):
        abort(404)

    process = subprocess.Popen(
        ["yt-dlp", "-f", "bestaudio", "-o", "-", PLAYLIST[index]],
        stdout=subprocess.PIPE,
        stderr=subprocess.DEVNULL
    )

    def stream():
        try:
            while True:
                chunk = process.stdout.read(8192)
                if not chunk:
                    break
                yield chunk
        finally:
            process.kill()

    return Response(
        stream_with_context(stream()),
        mimetype="audio/mpeg"
    )


if __name__ == "__main__":
    if len(sys.argv) <= 1:
        print("ERROR: Specify link to YouTube playlist")
    playlist_url = sys.argv[1]
    print("Converting Playlist to YouTube Links")
    PLAYLIST = convert_playlist_to_links(playlist_url)
    print(PLAYLIST)
    print(f"OK. Now serving, you can access the m3u at {BASE_URL}/playlist.m3u")
    app.run(host="0.0.0.0", port=8000, threaded=True)
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage