Add channel URL lookup and auto-detection

- Accept YouTube URLs in any format (@handle, /c/, /user/, /channel/)
- Auto-extract channel ID from URLs using yt-dlp
- Auto-detect channel name from URL (optional field)
- Support direct channel ID input (backwards compatible)
- Prevent duplicate channels
- Updated UI with better instructions and examples
- Improved user experience - just paste channel URL
This commit is contained in:
2026-05-08 19:19:41 -04:00
parent b3f378a8ef
commit 3726cb5775
2 changed files with 100 additions and 18 deletions

75
app.py
View File

@@ -7,8 +7,10 @@ Provides a web UI for managing YouTube channel downloads
import os import os
import subprocess import subprocess
import json import json
import re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from urllib.parse import urlparse, parse_qs
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
app = Flask(__name__) app = Flask(__name__)
@@ -45,6 +47,51 @@ def save_settings(settings):
with open(SETTINGS_FILE, 'w') as f: with open(SETTINGS_FILE, 'w') as f:
json.dump(settings, f, indent=2) json.dump(settings, f, indent=2)
def extract_channel_id(url_or_id):
"""
Extract channel ID from various YouTube URL formats or validate direct ID
Returns tuple: (channel_id, channel_name_hint or None)
"""
# If it looks like a channel ID already (24 characters starting with UC)
if re.match(r'^UC[\w-]{22}$', url_or_id.strip()):
return url_or_id.strip(), None
# Parse as URL
try:
parsed = urlparse(url_or_id)
# Format: youtube.com/channel/CHANNEL_ID
if '/channel/' in parsed.path:
channel_id = parsed.path.split('/channel/')[-1].split('/')[0]
if channel_id:
return channel_id, None
# Format: youtube.com/@handle or youtube.com/c/name or youtube.com/user/name
# These require fetching the page to get the actual channel ID
if parsed.netloc in ['youtube.com', 'www.youtube.com', 'm.youtube.com']:
# Use yt-dlp to extract channel ID from URL
try:
result = subprocess.run(
['yt-dlp', '--dump-json', '--playlist-items', '0', url_or_id],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0 and result.stdout:
data = json.loads(result.stdout.split('\n')[0])
channel_id = data.get('channel_id')
channel_name = data.get('channel')
if channel_id:
return channel_id, channel_name
except:
pass
except:
pass
return None, None
def get_channels(): def get_channels():
"""Read channels from channels.txt""" """Read channels from channels.txt"""
channels = [] channels = []
@@ -166,18 +213,38 @@ def channels():
@app.route('/channels/add', methods=['POST']) @app.route('/channels/add', methods=['POST'])
def add_channel(): def add_channel():
"""Add a new channel""" """Add a new channel"""
channel_id = request.form.get('channel_id', '').strip() channel_input = request.form.get('channel_id', '').strip()
channel_name = request.form.get('channel_name', '').strip() channel_name = request.form.get('channel_name', '').strip()
if not channel_id or not channel_name: if not channel_input:
flash('Channel ID and Name are required', 'error') flash('Channel ID or URL is required', 'error')
return redirect(url_for('channels')) return redirect(url_for('channels'))
# Extract channel ID from URL or validate direct ID
channel_id, auto_name = extract_channel_id(channel_input)
if not channel_id:
flash('Invalid channel ID or URL. Please check and try again.', 'error')
return redirect(url_for('channels'))
# Use auto-detected name if no name was provided
if not channel_name and auto_name:
channel_name = auto_name
elif not channel_name:
flash('Channel name is required (could not auto-detect from URL)', 'error')
return redirect(url_for('channels'))
# Check for duplicates
channels = get_channels() channels = get_channels()
for existing in channels:
if existing['id'] == channel_id:
flash(f'Channel already exists: {existing["name"]}', 'error')
return redirect(url_for('channels'))
channels.append({'id': channel_id, 'name': channel_name}) channels.append({'id': channel_id, 'name': channel_name})
save_channels(channels) save_channels(channels)
flash(f'Added channel: {channel_name}', 'success') flash(f'Added channel: {channel_name} ({channel_id})', 'success')
return redirect(url_for('channels')) return redirect(url_for('channels'))
@app.route('/channels/delete/<int:index>') @app.route('/channels/delete/<int:index>')

View File

@@ -11,17 +11,20 @@
<h3>Add New Channel</h3> <h3>Add New Channel</h3>
<form method="POST" action="{{ url_for('add_channel') }}" class="form"> <form method="POST" action="{{ url_for('add_channel') }}" class="form">
<div class="form-group"> <div class="form-group">
<label for="channel_id">Channel ID <span class="required">*</span></label> <label for="channel_id">Channel URL or ID <span class="required">*</span></label>
<input type="text" id="channel_id" name="channel_id" required <input type="text" id="channel_id" name="channel_id" required
placeholder="UCfDNi1aEljAQ17mUrfUjkvg" class="form-control"> placeholder="https://www.youtube.com/@AltonBrown or UCfDNi1aEljAQ17mUrfUjkvg" class="form-control">
<small class="form-help">Find this in the channel URL or page source</small> <small class="form-help">
Paste any YouTube channel URL or enter the channel ID directly<br>
<strong>Supported formats:</strong> youtube.com/@handle, youtube.com/channel/ID, youtube.com/c/name, youtube.com/user/name
</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="channel_name">Channel Name <span class="required">*</span></label> <label for="channel_name">Channel Name</label>
<input type="text" id="channel_name" name="channel_name" required <input type="text" id="channel_name" name="channel_name"
placeholder="Alton Brown" class="form-control"> placeholder="Alton Brown (optional - auto-detected from URL)" class="form-control">
<small class="form-help">Display name for the channel folder</small> <small class="form-help">Display name for the channel folder. Leave blank to auto-detect from URL.</small>
</div> </div>
<button type="submit" class="btn btn-primary">Add Channel</button> <button type="submit" class="btn btn-primary">Add Channel</button>
@@ -65,14 +68,26 @@
</div> </div>
<div class="card info-card"> <div class="card info-card">
<h3>How to Find Channel IDs</h3> <h3>How to Add Channels</h3>
<h4>Option 1: Paste the Channel URL (Easiest)</h4>
<ol> <ol>
<li>Go to the YouTube channel page</li> <li>Go to the YouTube channel you want to sync</li>
<li>Right-click and select "View Page Source" (or press Ctrl+U / Cmd+U)</li> <li>Copy the URL from your browser address bar</li>
<li>Search for "channelId" (Ctrl+F / Cmd+F)</li> <li>Paste it into the "Channel URL or ID" field above</li>
<li>Copy the value that looks like: <code>UCfDNi1aEljAQ17mUrfUjkvg</code></li> <li>Click "Add Channel" - the name will be detected automatically!</li>
</ol> </ol>
<p><strong>Alternative:</strong> Some channel URLs include the ID directly:<br>
<code>youtube.com/channel/<strong>CHANNEL_ID_HERE</strong>/videos</code></p> <h4>Option 2: Enter Channel ID Directly</h4>
<p>If you have the channel ID (starts with "UC" and is 24 characters long), just paste it directly.</p>
<p><strong>Example:</strong> <code>UCfDNi1aEljAQ17mUrfUjkvg</code></p>
<h4>Supported URL Formats</h4>
<ul>
<li><code>https://www.youtube.com/@AltonBrown</code> - Modern @ handle format</li>
<li><code>https://www.youtube.com/channel/UCfDNi1aEljAQ17mUrfUjkvg</code> - Channel ID format</li>
<li><code>https://www.youtube.com/c/AltonBrown</code> - Custom URL format</li>
<li><code>https://www.youtube.com/user/altonbrown</code> - Legacy user format</li>
</ul>
</div> </div>
{% endblock %} {% endblock %}