anycoder-ea96de45 / index.html
eubottura's picture
Upload folder using huggingface_hub
0d800b6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Douyin Video Downloader</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #000000;
--primary-hover: #333333;
--secondary: #F8F8F8;
--background: #FFFFFF;
--surface: #FFFFFF;
--text-primary: #000000;
--text-secondary: #666666;
--text-tertiary: #999999;
--border: #E5E5E5;
--border-hover: #CCCCCC;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.08);
--radius: 8px;
--radius-lg: 12px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'Inter', sans-serif;
background: var(--background);
min-height: 100vh;
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
}
header {
text-align: center;
margin-bottom: 3rem;
}
h1 {
font-size: 2rem;
font-weight: 600;
letter-spacing: -0.01em;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.subtitle {
color: var(--text-secondary);
font-size: 0.95rem;
font-weight: 400;
}
.credit {
margin-top: 2rem;
font-size: 0.8rem;
color: var(--text-tertiary);
}
.credit a {
color: var(--text-secondary);
text-decoration: none;
transition: color 0.2s ease;
}
.credit a:hover {
color: var(--text-primary);
text-decoration: underline;
}
.main-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
box-shadow: var(--shadow-sm);
padding: 2.5rem;
}
.notice {
padding: 1rem;
border-radius: var(--radius);
font-size: 0.875rem;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.cors-notice {
background: #F8F8F8;
border: 1px solid var(--border);
color: var(--text-secondary);
}
.success-notice {
background: #F0F9F0;
border: 1px solid #D0E7D0;
color: #2D5A2D;
display: none;
}
.info-box {
background: #F8F9FA;
border: 1px solid var(--border);
color: var(--text-secondary);
}
.input-section {
margin-bottom: 2rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.9rem;
color: var(--text-primary);
}
textarea {
width: 100%;
min-height: 140px;
padding: 0.875rem;
background: var(--secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text-primary);
font-size: 0.875rem;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
resize: vertical;
transition: all 0.2s ease;
line-height: 1.5;
}
textarea:focus {
outline: none;
border-color: var(--text-primary);
background: var(--surface);
box-shadow: 0 0 0 1px var(--text-primary);
}
textarea::placeholder {
color: var(--text-tertiary);
}
.button-group {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
flex-wrap: wrap;
}
button {
padding: 0.625rem 1.25rem;
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
button:hover {
background: var(--primary-hover);
}
button.secondary {
background: var(--secondary);
color: var(--text-primary);
border: 1px solid var(--border);
}
button.secondary:hover {
background: var(--surface);
border-color: var(--border-hover);
}
.loading {
display: none;
text-align: center;
padding: 2rem;
}
.loading.show {
display: block;
}
.spinner {
width: 32px;
height: 32px;
border: 2px solid var(--border);
border-top-color: var(--text-primary);
border-radius: 50%;
margin: 0 auto 1rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.message {
padding: 0.875rem;
border-radius: var(--radius);
font-size: 0.875rem;
margin-top: 1rem;
display: none;
}
.error-message {
background: #FFF5F5;
border: 1px solid #FED7D7;
color: #742A2A;
}
.info-message {
background: #F0F7FF;
border: 1px solid #BEE3F8;
color: #2A4365;
}
.log-box {
background: var(--secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.875rem;
margin: 1rem 0;
font-family: 'SF Mono', 'Monaco', monospace;
font-size: 0.75rem;
max-height: 180px;
overflow-y: auto;
display: none;
}
.log-entry {
margin: 0.25rem 0;
color: var(--text-secondary);
}
.log-entry.success {
color: #2D5A2D;
}
.log-entry.error {
color: #742A2A;
}
.log-entry.info {
color: #2A4365;
}
.results-section {
margin-top: 2rem;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
}
.results-section.show {
opacity: 1;
transform: translateY(0);
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border);
}
.results-title {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.copy-all-btn {
padding: 0.5rem 1rem;
font-size: 0.8rem;
}
.url-list {
list-style: none;
}
.url-item {
padding: 1rem;
margin-bottom: 0.5rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
display: flex;
align-items: center;
gap: 0.75rem;
transition: all 0.2s ease;
animation: slideIn 0.3s ease forwards;
opacity: 0;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.url-item:hover {
border-color: var(--border-hover);
box-shadow: var(--shadow-sm);
}
.url-content {
flex: 1;
word-break: break-all;
font-family: 'SF Mono', 'Monaco', monospace;
font-size: 0.8rem;
color: var(--text-secondary);
}
.url-content.v3-link {
color: var(--text-primary);
font-weight: 500;
}
.copy-btn {
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
background: var(--secondary);
color: var(--text-primary);
border: 1px solid var(--border);
min-width: auto;
}
.copy-btn.copied {
background: #2D5A2D;
color: white;
border-color: #2D5A2D;
}
.status-indicator {
width: 5px;
height: 5px;
border-radius: 50%;
margin-left: 0.5rem;
}
.status-indicator.success {
background: #2D5A2D;
}
.status-indicator.fallback {
background: #742A2A;
}
.status-indicator.processing {
background: #744210;
}
.quality-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 500;
margin-left: 0.5rem;
}
.quality-badge {
background: #F0F9F0;
color: #2D5A2D;
}
.quality-badge.fallback {
background: #FFF5F5;
color: #742A2A;
}
.quality-badge.processing {
background: #FFFBF0;
color: #744210;
}
@media (max-width: 640px) {
.container {
padding: 2rem 1rem;
}
h1 {
font-size: 1.75rem;
}
.main-card {
padding: 1.5rem;
}
.button-group {
flex-direction: column;
}
button {
width: 100%;
}
.results-header {
flex-direction: column;
gap: 0.75rem;
align-items: stretch;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Video Downloader</h1>
<p class="subtitle">Extract direct download links</p>
<p class="credit">Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</p>
</header>
<main class="main-card">
<div class="notice cors-notice">
<strong>Enhanced Processing:</strong> Multiple extraction methods applied.
</div>
<div class="notice success-notice" id="successNotice">
<strong>Success!</strong> Links extracted successfully.
</div>
<div class="notice info-box">
<strong>Processing Strategy:</strong>
<br>1. Extract video ID
<br>2. Try multiple API endpoints
<br>3. Parse for download links
<br>4. Sort by quality
</div>
<section class="input-section">
<label for="urlInput">Enter Video URLs (one per line):</label>
<textarea
id="urlInput"
placeholder="https://www.douyin.com/video/7123456789012345678&#10;https://v.douyin.com/ABCDEFG123456/&#10;https://www.douyin.com/user/987654321?modal_id=7123456789012345678"
></textarea>
<div class="button-group">
<button onclick="processUrls()">Process URLs</button>
<button class="secondary" onclick="clearInput()">Clear</button>
<button class="secondary" onclick="loadSample()">Load Sample</button>
<button class="secondary" onclick="toggleDebug()">Debug</button>
</div>
</section>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Extracting links...</p>
</div>
<div class="message error-message" id="errorMessage"></div>
<div class="message info-message" id="infoMessage"></div>
<div class="log-box" id="logBox"></div>
<section class="results-section" id="resultsSection">
<div class="results-header">
<h2 class="results-title">
Download Links
<span class="status-indicator success"></span>
</h2>
<button class="copy-all-btn" onclick="copyAllUrls()">Copy All</button>
</div>
<ul class="url-list" id="urlList"></ul>
</section>
</main>
</div>
<script>
let debugMode = false;
function log(message, type = 'info') {
if (!debugMode) return;
const logBox = document.getElementById('logBox');
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logBox.appendChild(entry);
logBox.scrollTop = logBox.scrollHeight;
console.log(`[${type.toUpperCase()}] ${message}`);
}
function toggleDebug() {
debugMode = !debugMode;
const logBox = document.getElementById('logBox');
if (debugMode) {
logBox.classList.add('show');
logBox.style.display = 'block';
log('Debug mode enabled', 'info');
} else {
logBox.classList.remove('show');
logBox.style.display = 'none';
}
}
function urlEncode(str) {
return encodeURIComponent(str);
}
function base64Encode(str) {
return btoa(unescape(encodeURIComponent(str)));
}
function extractVideoId(url) {
log(`Extracting video ID from: ${url}`, 'info');
const urlObj = new URL(url);
const modalId = urlObj.searchParams.get('modal_id');
if (modalId) {
log(`Found modal_id: ${modalId}`, 'success');
return modalId;
}
const pathMatch = url.match(/\/video\/(\d+)/);
if (pathMatch) {
log(`Found video ID in path: ${pathMatch[1]}`, 'success');
return pathMatch[1];
}
log('No video ID found, might be short URL', 'error');
return null;
}
async function processWithSnapdouyin(videoId, originalUrl) {
log(`Trying snapdouyin API for video ID: ${videoId}`, 'info');
const targetUrl = `https://www.douyin.com/video/${videoId}`;
const hash = base64Encode(targetUrl) + (targetUrl.length + 1000) + base64Encode('aio-dl');
const endpoints = [
'https://snapdouyin.app/wp-json/mx-downloader/video-data/',
'https://snapdouyin.app/wp-json/aio-video-downloader/v1/video-info/',
'https://snapdouyin.app/api/video-info'
];
for (const endpoint of endpoints) {
try {
log(`Trying endpoint: ${endpoint}`, 'info');
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
body: `url=${urlEncode(targetUrl)}&hash=${hash}`
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
log(`Response received: ${JSON.stringify(data).substring(0, 200)}...`, 'info');
if (data.medias && data.medias.length > 0) {
const sortedMedias = data.medias.sort((a, b) => (b.size || 0) - (a.size || 0));
const best = sortedMedias[0];
if (best.url && best.url.includes('v3')) {
log(`Found V3 link: ${best.url}`, 'success');
return {
url: best.url,
size: best.size,
formattedSize: best.formattedSize || `${(best.size / 1000000).toFixed(1)} MB`,
quality: best.quality || 'V3',
source: 'snapdouyin'
};
}
}
if (data.video_url) {
log(`Found video_url: ${data.video_url}`, 'success');
return {
url: data.video_url,
source: 'snapdouyin-alt'
};
}
} catch (error) {
log(`Endpoint ${endpoint} failed: ${error.message}`, 'error');
}
}
return null;
}
async function processWithAlternative(videoId) {
log(`Trying alternative API for video ID: ${videoId}`, 'info');
const endpoints = [
'https://api.douyin.wtf/api',
'https://douyin.wtf/api',
'https://tikmate.online/api/douyin'
];
for (const endpoint of endpoints) {
try {
log(`Trying alternative endpoint: ${endpoint}`, 'info');
const response = await fetch(`${endpoint}?url=https://www.douyin.com/video/${videoId}`, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
log(`Alternative API response: ${JSON.stringify(data).substring(0, 200)}...`, 'info');
if (data.data && data.data.play_url) {
log(`Found play_url in alternative API`, 'success');
return {
url: data.data.play_url,
source: 'alternative'
};
}
if (data.video_url) {
log(`Found video_url in alternative API`, 'success');
return {
url: data.video_url,
source: 'alternative'
};
}
} catch (error) {
log(`Alternative endpoint ${endpoint} failed: ${error.message}`, 'error');
}
}
return null;
}
async function processWithDirectScraping(videoId) {
log(`Trying direct scraping approach for video ID: ${videoId}`, 'info');
try {
const apiUrl = `https://www.iesdouyin.com/share/video/${videoId}/?region=CN&mid=${videoId}`;
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
'Referer': 'https://www.douyin.com/'
}
});
if (response.ok) {
const text = await response.text();
log(`Page content received, length: ${text.length}`, 'info');
const urlMatch = text.match(/"play_addr":\s*{\s*"url_list":\s*\["([^"]+)"/);
if (urlMatch && urlMatch[1]) {
log(`Extracted URL from page: ${urlMatch[1]}`, 'success');
return {
url: urlMatch[1].replace(/\\/g, ''),
source: 'direct-scraping'
};
}
}
} catch (error) {
log(`Direct scraping failed: ${error.message}`, 'error');
}
return null;
}
async function processSingleUrl(inputUrl) {
log(`\n=== Processing URL: ${inputUrl} ===`, 'info');
try {
let videoId = extractVideoId(inputUrl);
if (!videoId && inputUrl.includes('v.douyin.com')) {
log('Attempting to resolve short URL', 'info');
try {
const response = await fetch(inputUrl, {
method: 'HEAD',
redirect: 'manual'
});
const location = response.headers.get('location');
if (location) {
videoId = extractVideoId(location);
log(`Resolved short URL to: ${location}`, 'success');
}
} catch (error) {
log('Could not resolve short URL due to CORS', 'error');
}
}
if (!videoId) {
throw new Error('Could not extract video ID');
}
let result = null;
result = await processWithSnapdouyin(videoId, inputUrl);
if (result) {
log('Success with snapdouyin API', 'success');
return result;
}
result = await processWithAlternative(videoId);
if (result) {
log('Success with alternative API', 'success');
return result;
}
result = await processWithDirectScraping(videoId);
if (result) {
log('Success with direct scraping', 'success');
return result;
}
log('All methods failed, using fallback', 'error');
const fallbackUrl = `https://snapdouyin.app/#url=${urlEncode(inputUrl)}`;
return {
url: fallbackUrl,
isFallback: true,
source: 'fallback',
error: 'Could not extract V3 link'
};
} catch (error) {
log(`Processing failed: ${error.message}`, 'error');
const fallbackUrl = `https://snapdouyin.app/#url=${urlEncode(inputUrl)}`;
return {
url: fallbackUrl,
isFallback: true,
source: 'error-fallback',
error: error.message
};
}
}
async function processUrls() {
const input = document.getElementById('urlInput').value.trim();
if (!input) {
showError('Please enter at least one Douyin URL');
return;
}
const urls = input.split('\n').filter(url => url.trim());
if (urls.length === 0) {
showError('Please enter valid URLs');
return;
}
hideMessages();
document.getElementById('loading').classList.add('show');
document.getElementById('resultsSection').classList.remove('show');
document.getElementById('successNotice').classList.remove('show');
const results = [];
let successCount = 0;
for (let i = 0; i < urls.length; i++) {
const url = urls[i].trim();
log(`\nπŸ”„ Processing URL ${i + 1}/${urls.length}`, 'info');
try {
const result = await processSingleUrl(url);
results.push(result);
if (!result.isFallback) {
successCount++;
}
if (i < urls.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
log(`Failed to process URL ${i + 1}: ${error.message}`, 'error');
results.push({
url: `https://snapdouyin.app/#url=${urlEncode(url)}`,
isFallback: true,
source: 'catch-fallback',
error: error.message
});
}
}
document.getElementById('loading').classList.remove('show');
displayResults(results, successCount);
if (successCount > 0) {
document.getElementById('successNotice').classList.add('show');
document.getElementById('successNotice').style.display = 'flex';
showInfo(`Successfully extracted ${successCount} link(s) out of ${urls.length} URLs`);
}
}
function displayResults(results, successCount) {
const urlList = document.getElementById('urlList');
urlList.innerHTML = '';
results.forEach((result, index) => {
const li = document.createElement('li');
li.className = 'url-item';
li.style.animationDelay = `${index * 0.05}s}`;
const isFallback = result.isFallback;
const isV3 = !isFallback && (result.url.includes('v3') || result.quality === 'V3');
let qualityBadge = '';
let statusClass = 'success';
if (isV3) {
qualityBadge = `<span class="quality-badge">βœ“ V3 ${result.quality || 'HD'} β€’ ${result.formattedSize || 'Unknown'}</span>`;
} else if (isFallback) {
qualityBadge = '<span class="quality-badge fallback">βœ— Fallback</span>';
statusClass = 'fallback';
} else {
qualityBadge = `<span class="quality-badge">βœ“ ${result.quality || 'Unknown'}</span>`;
}
let sourceInfo = '';
if (result.source) {
sourceInfo = `<small style="color: var(--text-tertiary); font-size: 0.7rem;">[${result.source}]</small>`;
}
const urlContentClass = isV3 ? 'url-content v3-link' : 'url-content';
li.innerHTML = `
<div class="${urlContentClass}">${result.url}</div>
${qualityBadge}
${sourceInfo}
<button class="copy-btn" onclick="copyUrl('${result.url.replace(/'/g, "\\'")}', this)">Copy</button>
<span class="status-indicator ${statusClass}"></span>
`;
urlList.appendChild(li);
});
document.getElementById('resultsSection').classList.add('show');
}
function copyUrl(url, button) {
navigator.clipboard.writeText(url).then(() => {
button.textContent = 'βœ“ Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = 'Copy';
button.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
}
function copyAllUrls() {
const urls = Array.from(document.querySelectorAll('.url-content')).map(el => el.textContent);
const text = urls.map((url, index) => `${index + 1}. ${url}`).join('\n');
navigator.clipboard.writeText(text).then(() => {
const button = document.querySelector('.copy-all-btn');
const originalText = button.textContent;
button.textContent = 'βœ“ Copied All!';
button.style.background = '#2D5A2D';
button.style.color = 'white';
setTimeout(() => {
button.textContent = originalText;
button.style.background = '';
button.style.color = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy all:', err);
});
}
function clearInput() {
document.getElementById('urlInput').value = '';
document.getElementById('resultsSection').classList.remove('show');
hideMessages();
if (debugMode) {
document.getElementById('logBox').innerHTML = '';
log('Debug mode enabled', 'info');
}
}
function loadSample() {
const sampleUrls = `https://www.douyin.com/video/7300000012345678901?modal_id=7300000012345678901
https://v.douyin.com/ieFQDjC/
https://www.douyin.com/video/7300000023456789012
https://www.douyin.com/user/MS4wLjABAAAA5rOKyL98?modal_id=7300000034567890123`;
document.getElementById('urlInput').value = sampleUrls;
}
function showError(message) {
hideMessages();
const errorElement = document.getElementById('errorMessage');
errorElement.textContent = message;
errorElement.style.display = 'block';
}
function showInfo(message) {
hideMessages();
const infoElement = document.getElementById('infoMessage');
infoElement.textContent = message;
infoElement.style.display = 'block';
}
function hideMessages() {
document.getElementById('errorMessage').style.display = 'none';
document.getElementById('infoMessage').style.display = 'none';
document.getElementById('successNotice').classList.remove('show');
document.getElementById('successNotice').style.display = 'none';
}
document.getElementById('urlInput').addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
processUrls();
}
if (e.ctrlKey && e.key === 'l') {
e.preventDefault();
clearInput();
}
if (e.ctrlKey && e.key === 'd') {
e.preventDefault();
toggleDebug();
}
});
console.log('Video Downloader Ready');
</script>
</body>
</html>