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:
75
app.py
75
app.py
@@ -7,8 +7,10 @@ Provides a web UI for managing YouTube channel downloads
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -45,6 +47,51 @@ def save_settings(settings):
|
||||
with open(SETTINGS_FILE, 'w') as f:
|
||||
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():
|
||||
"""Read channels from channels.txt"""
|
||||
channels = []
|
||||
@@ -166,18 +213,38 @@ def channels():
|
||||
@app.route('/channels/add', methods=['POST'])
|
||||
def add_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()
|
||||
|
||||
if not channel_id or not channel_name:
|
||||
flash('Channel ID and Name are required', 'error')
|
||||
if not channel_input:
|
||||
flash('Channel ID or URL is required', 'error')
|
||||
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()
|
||||
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})
|
||||
save_channels(channels)
|
||||
|
||||
flash(f'Added channel: {channel_name}', 'success')
|
||||
flash(f'Added channel: {channel_name} ({channel_id})', 'success')
|
||||
return redirect(url_for('channels'))
|
||||
|
||||
@app.route('/channels/delete/<int:index>')
|
||||
|
||||
@@ -11,17 +11,20 @@
|
||||
<h3>Add New Channel</h3>
|
||||
<form method="POST" action="{{ url_for('add_channel') }}" class="form">
|
||||
<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
|
||||
placeholder="UCfDNi1aEljAQ17mUrfUjkvg" class="form-control">
|
||||
<small class="form-help">Find this in the channel URL or page source</small>
|
||||
placeholder="https://www.youtube.com/@AltonBrown or UCfDNi1aEljAQ17mUrfUjkvg" class="form-control">
|
||||
<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 class="form-group">
|
||||
<label for="channel_name">Channel Name <span class="required">*</span></label>
|
||||
<input type="text" id="channel_name" name="channel_name" required
|
||||
placeholder="Alton Brown" class="form-control">
|
||||
<small class="form-help">Display name for the channel folder</small>
|
||||
<label for="channel_name">Channel Name</label>
|
||||
<input type="text" id="channel_name" name="channel_name"
|
||||
placeholder="Alton Brown (optional - auto-detected from URL)" class="form-control">
|
||||
<small class="form-help">Display name for the channel folder. Leave blank to auto-detect from URL.</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Add Channel</button>
|
||||
@@ -65,14 +68,26 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<li>Go to the YouTube channel page</li>
|
||||
<li>Right-click and select "View Page Source" (or press Ctrl+U / Cmd+U)</li>
|
||||
<li>Search for "channelId" (Ctrl+F / Cmd+F)</li>
|
||||
<li>Copy the value that looks like: <code>UCfDNi1aEljAQ17mUrfUjkvg</code></li>
|
||||
<li>Go to the YouTube channel you want to sync</li>
|
||||
<li>Copy the URL from your browser address bar</li>
|
||||
<li>Paste it into the "Channel URL or ID" field above</li>
|
||||
<li>Click "Add Channel" - the name will be detected automatically!</li>
|
||||
</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>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user