feat(instagram): use yt-dlp as a downloader

This commit is contained in:
Kristofers Solo 2025-10-27 10:02:39 +02:00
parent 47ab326e06
commit a276585b25
Signed by: kristoferssolo
GPG Key ID: 74FF8144483D82C8
3 changed files with 37 additions and 37 deletions

View File

@ -36,9 +36,7 @@ RUN apt-get update -y \
RUN --mount=type=cache,target=/root/.cache/uv RUN --mount=type=cache,target=/root/.cache/uv
# Intstall deps # Intstall deps
RUN uv tool install instaloader \ RUN uv tool install yt-dlp[default] \
&& instaloader --version \
&& uv tool install yt-dlp[default] \
&& yt-dlp --version && yt-dlp --version
WORKDIR /app WORKDIR /app

View File

@ -110,23 +110,23 @@ async fn run_command_in_tempdir(cmd: &str, args: &[&str]) -> Result<DownloadResu
/// ///
/// - Propagates `run_command_in_tempdir` errors. /// - Propagates `run_command_in_tempdir` errors.
#[cfg(feature = "instagram")] #[cfg(feature = "instagram")]
pub async fn download_instaloader(shortcode: &str) -> Result<DownloadResult> { pub async fn download_instaloader(url: &str) -> Result<DownloadResult> {
fn get_env_var(name: &str) -> Result<String> { let base_args = ["--extractor-args", "instagram:"];
env::var(name).map_err(|_| Error::env(name)) let mut args = base_args
} .iter()
let session_file = get_env_var("IG_SESSION_PATH")?; .map(ToString::to_string)
.collect::<Vec<_>>();
let args = [ let cookies_path = env::var("COOKIES_PATH").ok();
"--sessionfile", if let Some(cookies) = read_cookies_file(cookies_path) {
&session_file, args.extend(["--cookies".into(), cookies]);
"--dirname-pattern=.", }
"--no-metadata-json",
"--no-compress-json", args.push(url.into());
"--quiet",
"--", let args_ref = args.iter().map(String::as_ref).collect::<Vec<_>>();
&format!("-{shortcode}"),
]; run_command_in_tempdir("yt-dlp", &args_ref).await
run_command_in_tempdir("instaloader", &args).await
} }
/// Download a URL with yt-dlp. /// Download a URL with yt-dlp.
@ -135,10 +135,7 @@ pub async fn download_instaloader(shortcode: &str) -> Result<DownloadResult> {
/// ///
/// - Propagates `run_command_in_tempdir` errors. /// - Propagates `run_command_in_tempdir` errors.
#[cfg(feature = "youtube")] #[cfg(feature = "youtube")]
pub async fn download_ytdlp<P: AsRef<Path>>( pub async fn download_ytdlp(url: &str) -> Result<DownloadResult> {
url: &str,
cookies_path: Option<P>,
) -> Result<DownloadResult> {
let base_args = [ let base_args = [
"--no-playlist", "--no-playlist",
"-t", "-t",
@ -148,8 +145,9 @@ pub async fn download_ytdlp<P: AsRef<Path>>(
"%(title)s.%(ext)s", "%(title)s.%(ext)s",
"--no-warnings", "--no-warnings",
"--quiet", "--quiet",
"-f",
"--postprocessor-args", "--postprocessor-args",
"ffmpeg:-vf setsar=1 -c:v libx264 -crf 20 -preset veryfast -c:a aac -b:a 128k -movflags +faststart", "ffmpeg:-vf setsar=1 -c:v libx264 -crf 28 -preset ultrafast -maxrate 800k -bufsize 1600k -vf scale=854:480 -c:a aac -b:a 64k -movflags +faststart",
]; ];
let mut args = base_args let mut args = base_args
@ -157,14 +155,9 @@ pub async fn download_ytdlp<P: AsRef<Path>>(
.map(ToString::to_string) .map(ToString::to_string)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if let Some(cookie_path) = cookies_path { let cookies_path = env::var("COOKIES_PATH").ok();
let path = cookie_path.as_ref(); if let Some(cookies) = read_cookies_file(cookies_path) {
let path_str = path.to_string_lossy().to_string(); args.extend(["--cookies".into(), cookies]);
if path.exists() {
args.extend(["--cookies".into(), path_str]);
} else {
warn!("Cookies file not found: {path_str}");
}
} }
args.push(url.into()); args.push(url.into());
@ -174,6 +167,17 @@ pub async fn download_ytdlp<P: AsRef<Path>>(
run_command_in_tempdir("yt-dlp", &args_ref).await run_command_in_tempdir("yt-dlp", &args_ref).await
} }
fn read_cookies_file<P: AsRef<Path>>(cookies_path: Option<P>) -> Option<String> {
if let Some(cookie_path) = cookies_path {
let path = cookie_path.as_ref();
if path.exists() {
return Some(path.to_string_lossy().to_string());
}
warn!("Cookies file not found: {}", path.display());
}
None
}
/// Post-process a `DownloadResult`. /// Post-process a `DownloadResult`.
/// ///
/// Detect media kinds (async), prefer video, then image, then call `send_media_from_path`. /// Detect media kinds (async), prefer video, then image, then call `send_media_from_path`.

View File

@ -1,14 +1,13 @@
use crate::handlers::SocialHandler;
use crate::{ use crate::{
download::{download_ytdlp, process_download_result}, download::{download_ytdlp, process_download_result},
error::Result, error::Result,
}; };
use regex::Regex; use regex::Regex;
use std::{env, sync::OnceLock}; use std::sync::OnceLock;
use teloxide::{Bot, types::ChatId}; use teloxide::{Bot, types::ChatId};
use tracing::info; use tracing::info;
use crate::handlers::SocialHandler;
static SHORTCODE_RE: OnceLock<Regex> = OnceLock::new(); static SHORTCODE_RE: OnceLock<Regex> = OnceLock::new();
fn shortcode_regex() -> &'static Regex { fn shortcode_regex() -> &'static Regex {
@ -42,8 +41,7 @@ impl SocialHandler for YouTubeShortsHandler {
async fn handle(&self, bot: &Bot, chat_id: ChatId, url: String) -> Result<()> { async fn handle(&self, bot: &Bot, chat_id: ChatId, url: String) -> Result<()> {
info!(handler = %self.name(), url = %url, "handling youtube url"); info!(handler = %self.name(), url = %url, "handling youtube url");
let cookies_path = env::var("COOKIES_PATH"); let dr = download_ytdlp(&url).await?;
let dr = download_ytdlp(&url, cookies_path.as_deref().ok()).await?;
process_download_result(bot, chat_id, dr).await process_download_result(bot, chat_id, dr).await
} }