From ed1ee70b01e8d0642b97948785c2bc530c1ddc7e Mon Sep 17 00:00:00 2001 From: Firoz Ahmed Date: Sat, 9 Nov 2024 22:52:05 +0530 Subject: [PATCH] Improve code quality and performance --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/firofame/live/tree/main?shareId=XXXX-XXXX-XXXX-XXXX). --- .github/workflows/python-app.yml | 7 +++ app.py | 83 +++++++++++++++++++------------- index.html | 4 +- styles.css | 8 +-- youtube-player.js | 53 +++++++++++--------- 5 files changed, 92 insertions(+), 63 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index af40726..00b2b1f 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -20,6 +20,13 @@ jobs: uses: actions/setup-python@v3 with: python-version: "3.10" + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/app.py b/app.py index a9ca1a8..8f17d48 100644 --- a/app.py +++ b/app.py @@ -2,51 +2,66 @@ import re import requests -# Read the handles from a file -with open('handles.txt', 'r') as f: - handles = [tuple(line.strip().split(',')) for line in f] +def read_handles(file_path): + with open(file_path, 'r') as f: + return [tuple(line.strip().split(',')) for line in f] -# Create an empty list to store the channel data -channel_data = [] - -# Write the playlist file -with open('playlist.m3u', 'w') as f: - f.write('#EXTM3U\n') - for i, (handle, group) in enumerate(handles, start=1): - # Construct the URL for the channel's live stream - url = f'https://www.youtube.com/@{handle}/live' - - # Send a GET request to the URL +def fetch_channel_data(handle): + url = f'https://www.youtube.com/@{handle}/live' + try: response = requests.get(url) + response.raise_for_status() + except requests.RequestException as e: + print(f"Error fetching data for handle {handle}: {e}") + return None - # Search for the ytInitialData variable in the page source - match = re.search(r'var ytInitialData = ({.*?});', response.text) + match = re.search(r'var ytInitialData = ({.*?});', response.text) + if not match: + print(f"No ytInitialData found for handle {handle}") + return None - # Extract the JSON data from the match + try: json_data = match.group(1) - - # Parse the JSON data data = json.loads(json_data) + except json.JSONDecodeError as e: + print(f"Error parsing JSON data for handle {handle}: {e}") + return None - # Extract the channel name and image URL + try: channel_name = data['contents']['twoColumnWatchNextResults']['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['title']['runs'][0]['text'] image_url = data['contents']['twoColumnWatchNextResults']['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['thumbnail']['thumbnails'][-1]['url'] - - # Extract the channel ID channel_id = data['contents']['twoColumnWatchNextResults']['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['title']['runs'][0]['navigationEndpoint']['browseEndpoint']['browseId'] + except (KeyError, IndexError) as e: + print(f"Error extracting channel data for handle {handle}: {e}") + return None - # Construct the M3U8 URL for the channel's live stream - m3u8_url = f'https://ythls.armelin.one/channel/{channel_id}.m3u8' + return { + 'channel_name': channel_name, + 'image_url': image_url, + 'channel_id': channel_id + } - # Write the metadata and URL to the playlist file - f.write(f'#EXTINF:-1 tvg-logo="{image_url}" group-title="{group}", {channel_name}\n{m3u8_url}\n') +def write_playlist(file_path, handles): + with open(file_path, 'w') as f: + f.write('#EXTM3U\n') + for handle, group in handles: + channel_data = fetch_channel_data(handle) + if channel_data: + m3u8_url = f'https://ythls.armelin.one/channel/{channel_data["channel_id"]}.m3u8' + f.write(f'#EXTINF:-1 tvg-logo="{channel_data["image_url"]}" group-title="{group}", {channel_data["channel_name"]}\n{m3u8_url}\n') - # Add the channel data to the list - channel_data.append({ - 'channel_name': channel_name, - 'channel_id': channel_id - }) +def write_channel_data(file_path, handles): + channel_data = [] + for handle, group in handles: + data = fetch_channel_data(handle) + if data: + channel_data.append({ + 'channel_name': data['channel_name'], + 'channel_id': data['channel_id'] + }) + with open(file_path, 'w') as f: + json.dump(channel_data, f) -# Write the channel data to a JSON file -with open('playlist.json', 'w') as f: - json.dump(channel_data, f) +handles = read_handles('handles.txt') +write_playlist('playlist.m3u', handles) +write_channel_data('playlist.json', handles) diff --git a/index.html b/index.html index 32a48e1..217e7dd 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - YouTube Live Stream Viewer + YouTube Live Stream Channel Viewer @@ -18,4 +18,4 @@ - \ No newline at end of file + diff --git a/styles.css b/styles.css index 001c285..e0d772f 100644 --- a/styles.css +++ b/styles.css @@ -30,7 +30,7 @@ iframe { max-height: 30%; overflow-y: auto; } -button { +#button-container button { margin: 5px; padding: 10px; font-size: 14px; @@ -41,9 +41,9 @@ button { cursor: pointer; transition: background-color 0.3s; } -button:hover { +#button-container button:hover { background-color: #45a049; } -button.active { +#button-container button.active { background-color: #007bff; -} \ No newline at end of file +} diff --git a/youtube-player.js b/youtube-player.js index f8218d5..f7614eb 100644 --- a/youtube-player.js +++ b/youtube-player.js @@ -2,33 +2,40 @@ const iframe = document.getElementById('youtube-iframe'); let buttons = document.querySelectorAll('#button-container button'); async function loadPlaylist() { - const response = await fetch('playlist.json'); - const playlist = await response.json(); + try { + const response = await fetch('playlist.json'); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const playlist = await response.json(); - function changeChannel(channelId) { - iframe.src = `https://www.youtube.com/embed/live_stream?channel=${channelId}&autoplay=1`; - localStorage.setItem('lastChannel', channelId); - updateActiveButton(channelId); - } + function changeChannel(channelId) { + iframe.src = `https://www.youtube.com/embed/live_stream?channel=${channelId}&autoplay=1`; + localStorage.setItem('lastChannel', channelId); + updateActiveButton(channelId); + } - function updateActiveButton(channelId) { - buttons.forEach(btn => { - btn.classList.toggle('active', btn.dataset.channel === channelId); - }); - } + function updateActiveButton(channelId) { + buttons.forEach(btn => { + btn.classList.toggle('active', btn.dataset.channel === channelId); + }); + } - const lastChannel = localStorage.getItem('lastChannel') || playlist[1].channel_id; - changeChannel(lastChannel); + const lastChannel = localStorage.getItem('lastChannel') || playlist[1].channel_id; + changeChannel(lastChannel); - const buttonContainer = document.getElementById('button-container'); - playlist.forEach(channel => { - const button = document.createElement('button'); - button.dataset.channel = channel.channel_id; - button.textContent = channel.channel_name; - button.addEventListener('click', () => changeChannel(channel.channel_id)); - buttonContainer.appendChild(button); - }); - buttons = document.querySelectorAll('#button-container button'); + const buttonContainer = document.getElementById('button-container'); + playlist.forEach(channel => { + const button = document.createElement('button'); + button.dataset.channel = channel.channel_id; + button.textContent = channel.channel_name; + button.addEventListener('click', () => changeChannel(channel.channel_id)); + buttonContainer.appendChild(button); + }); + buttons = document.querySelectorAll('#button-container button'); + } catch (error) { + console.error('Error loading playlist:', error); + } } loadPlaylist();