feat: Add support for Twitter/X URL with Safari compatibility

This commit is contained in:
Peifan Li
2025-12-20 22:35:53 -05:00
parent 36abe664ed
commit 70c8538899
2 changed files with 24 additions and 10 deletions

View File

@@ -56,12 +56,18 @@ export function prepareDownloadFlags(
// Get format sort option if user specified it
const formatSortValue = userFormatSort || userFormatSort2;
// Check if this is a Twitter/X URL - always use mp4 for Safari compatibility
const isTwitterUrl =
videoUrl.includes("x.com") || videoUrl.includes("twitter.com");
// Determine merge output format: use user's choice or default to mp4
// However, if user is sorting by resolution (likely demanding 4K/VP9), default to MKV
// However, if user is sorting by resolution (likely demanding 4K/VP9), default to WebM
// because VP9/AV1 in MP4 (mp4v2) is often problematic for Safari/QuickTime.
// Exception: Twitter/X always uses mp4 for Safari compatibility
let defaultMergeFormat = "mp4";
if (formatSortValue && formatSortValue.includes("res")) {
if (!isTwitterUrl && formatSortValue && formatSortValue.includes("res")) {
// Use WebM for high-res (likely VP9/AV1) as it's supported by Safari 14+ and Chrome
// But skip this for Twitter/X to ensure Safari compatibility
defaultMergeFormat = "webm";
}
const mergeOutputFormat = userMergeOutputFormat || defaultMergeFormat;
@@ -84,6 +90,18 @@ export function prepareDownloadFlags(
logger.info("Using user-specified format sort:", formatSortValue);
}
// Add Twitter/X specific flags - always use MP4 with H.264 for Safari compatibility
if (isTwitterUrl) {
// Force MP4 format with H.264 codec for Safari compatibility
// Twitter/X videos should use MP4 container regardless of resolution
if (!config.f && !config.format) {
flags.format =
"bestvideo[ext=mp4][vcodec^=avc1]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best";
}
// Ensure merge output format is mp4 (already handled above, but log it)
logger.info("Twitter/X URL detected - using MP4 format for Safari compatibility");
}
// Add YouTube specific flags if it's a YouTube URL
// Always apply preferred formats for YouTube to ensure codec compatibility (H.264/AAC for Safari)
if (videoUrl.includes("youtube.com") || videoUrl.includes("youtu.be")) {

View File

@@ -16,8 +16,8 @@ import {
import * as storageService from "../../storageService";
import { Video } from "../../storageService";
import { BaseDownloader } from "../BaseDownloader";
import { getProviderScript } from "./ytdlpHelpers";
import { prepareDownloadFlags } from "./ytdlpConfig";
import { getProviderScript } from "./ytdlpHelpers";
import { extractVideoMetadata } from "./ytdlpMetadata";
import { processSubtitles } from "./ytdlpSubtitle";
@@ -112,7 +112,7 @@ export async function downloadVideo(
videoDescription = metadata.videoDescription;
thumbnailUrl = metadata.thumbnailUrl;
source = metadata.source;
// Extract channel URL from info if available
channelUrl = info.channel_url || info.uploader_url || null;
@@ -283,17 +283,14 @@ export async function downloadVideo(
title: videoTitle || "Video",
author: videoAuthor || "Unknown",
description: videoDescription,
date:
videoDate || new Date().toISOString().slice(0, 10).replace(/-/g, ""),
date: videoDate || new Date().toISOString().slice(0, 10).replace(/-/g, ""),
source: source, // Use extracted source
sourceUrl: videoUrl,
videoFilename: finalVideoFilename,
thumbnailFilename: thumbnailSaved ? finalThumbnailFilename : undefined,
thumbnailUrl: thumbnailUrl || undefined,
videoPath: `/videos/${finalVideoFilename}`,
thumbnailPath: thumbnailSaved
? `/images/${finalThumbnailFilename}`
: null,
thumbnailPath: thumbnailSaved ? `/images/${finalThumbnailFilename}` : null,
subtitles: subtitles.length > 0 ? subtitles : undefined,
duration: undefined, // Will be populated below
channelUrl: channelUrl || undefined,
@@ -369,4 +366,3 @@ export async function downloadVideo(
return videoData;
}