Mithronn / rusty_ytdl

A Rust library for Youtube video searcher and downloader
https://docs.rs/rusty_ytdl
MIT License
107 stars 20 forks source link

How to download as mp3 directly #15

Closed 0PandaDEV closed 7 months ago

0PandaDEV commented 8 months ago

I'm trying to write a YouTube downloader from a watch link in tauri and then apply the metadata for the song, but it does not apply the metadata, so I tried it in a separate rust project and found out that rusty_ytdl downloads the videos as webm not as mp3.

ffprobe ZbwEuFb2Zec.mp3
ffprobe version 6.1.1-essentials_build-www.gyan.dev Copyright (c) 2007-2023 the FFmpeg developers
  built with gcc 12.2.0 (Rev10, Built by MSYS2 project)
  configuration: --enable-gpl --enable-version3 --enable-static --pkg-config=pkgconf --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-dxva2 --enable-d3d11va --enable-libvpl --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberband
  libavutil      58. 29.100 / 58. 29.100
  libavcodec     60. 31.102 / 60. 31.102
  libavformat    60. 16.100 / 60. 16.100
  libavdevice    60.  3.100 / 60.  3.100
  libavfilter     9. 12.100 /  9. 12.100
  libswscale      7.  5.100 /  7.  5.100
  libswresample   4. 12.100 /  4. 12.100
  libpostproc    57.  3.100 / 57.  3.100
[matroska,webm @ 0000023ccf970c40] Discarding ID3 tags because more suitable tags were found.
Input #0, matroska,webm, from 'ZbwEuFb2Zec.mp3':
  Metadata:
    encoder         : google/video-file
  Duration: 00:02:18.02, start: -0.007000, bitrate: 133 kb/s
  Stream #0:0(eng): Audio: opus, 48000 Hz, stereo, fltp (default)
use anyhow::Result;
use id3::frame::PictureType;
use id3::TagLike;
use id3::{Tag, Version};
use reqwest;
use rusty_ytdl::Video;
use serde::Deserialize;
use std::path::PathBuf;

#[derive(Debug, Deserialize)]
struct ApiResponse {
    items: Vec<ApiItem>,
}

#[derive(Debug, Deserialize)]
struct ApiItem {
    #[serde(rename = "title")]
    title: Option<String>,
    #[serde(rename = "uploaderName")]
    uploader_name: Option<String>,
    #[serde(rename = "thumbnail")]
    thumbnail: Option<String>,
}

#[tauri::command]
pub async fn download(url: String, name: String) -> Result<()> {
    let video = Video::new(url.clone()).unwrap();

    let watch_id = name.trim_end_matches(".mp3");

    let api_url = format!("https://wireway.ch/api/musicAPI/search/?q={}", watch_id);
    let resp_body = reqwest::get(&api_url).await?.text().await?;
    let api_response: ApiResponse = serde_json::from_str(&resp_body)?;

    let mut path = PathBuf::new();
    match std::env::consts::OS {
        "macos" | "linux" => {
            let username = std::env::var("USER").unwrap_or_else(|_| "default".into());
            path.push(format!("/users/{}/Music/Vleer", username));
            if !path.exists() {
                std::fs::create_dir_all(&path).unwrap();
            }
        }
        "windows" => {
            let username = std::env::var("USERNAME").unwrap_or_else(|_| "default".into());
            path.push(format!("C:\\Users\\{}\\Music\\Vleer", username));
            if !path.exists() {
                std::fs::create_dir_all(&path).unwrap();
            }
        }
        _ => {}
    }
    path.push(&name);

    video.download(&path).await.unwrap();

    let mut tag = Tag::new();
    if let Some(first_item) = api_response.items.first() {
        tag.set_artist(
            first_item
                .uploader_name
                .as_ref()
                .unwrap_or(&String::from("Unknown Artist")),
        );
        tag.set_title(
            first_item
                .title
                .as_ref()
                .unwrap_or(&String::from("Unknown Title")),
        );

        if let Some(thumbnail_url) = &first_item.thumbnail {
            let response = reqwest::get(thumbnail_url).await?;
            if response.status().is_success() {
                let mut content: Vec<u8> = Vec::new();
                let body = response.bytes().await?;
                content.extend_from_slice(&body);
                let mime = "image/webp".to_string();
                tag.add_frame(id3::frame::Picture {
                    mime_type: mime,
                    picture_type: PictureType::CoverFront,
                    description: "Cover image".to_string(),
                    data: content,
                });
            }
        }
    }

    tag.write_to_path(&path, Version::Id3v23)?;

    println!(" {}", path.display());
    Ok(())
}
Mithronn commented 8 months ago

YouTube doesn't host MP3s. You can download webm then convert it to mp3 with FFmpeg

0PandaDEV commented 8 months ago

ok thanks

0PandaDEV commented 7 months ago

YouTube doesn't host MP3s. You can download webm then convert it to mp3 with FFmpeg

But then why does the example say .mp3 and that also works even though it shouldn't

Mithronn commented 7 months ago

I understand your confusion. But it's just a file name extension and it can be anything if the file contains a proper codec. In short, if you look at the downloaded file with the "ffmpeg -i " command, you can understand the codec of the file.

0PandaDEV commented 7 months ago

thanks i solved this issue already