前言: 很多人都不曉得在YouTube上面無論標題寫的什麼無損 flac, 24 bit 甚至 32 bit 音樂格式, 其實最高的音質就是 160kbps opus, 因為 youtube 都會將音樂部分以 opus 或是 mp4a (AAC 或 HEAAC) 格式重新壓縮過, 很多人對 opus 格式很陌生, 有興趣的網友可以自行 google opus 看看, 這個格式非常優秀, 有些網站透過頻域分析發現 opus 是在同樣的 bit rate 下音質最好的有損壓縮格式, 基本上 >= 160kbs 的 opus 格式的音樂就可被承認是 transparent (意思是幾乎無法區分與來源音樂的差異了), 故google 現已將所有影片聲音格式以 opus 方式來儲存(早期的影片也陸續在更換格式中, 有些還未更換的影片仍用 m4a), 其中最高的音質就設定在 160kbps opus, 例如下圖, 標題雖然說音樂是 24bit 192khz
https://youtu.be/D-h6MoF7HLA
但最後卻仍是 160 bps opus 為最佳儲存格式
用 youtube-dl -F https://youtu.be/D-h6MoF7HLA 查看所有格式
故一些 app 強調可以抓取 youtube 上的音樂,並以 flac 或是 wav 格式儲存, 其實只是將 opus 再轉成 flac或wav 而已, 它已不再是無損音樂, 只是充胖子而已, 所以若要抓取 youtube 上最高音質的音樂,就只有 160kbps opus。
正文開始, 想知道方法的直接跳到 5F 有完整的程式可以參考
我是分隔線===============
小弟想寫個shell script抓取youtube上的音樂(opus格式).
youtube-dl -f bestaudio -o %'(title)s%(ext)s' ${1} -x --audio-format "opus" --audio-quality 0
運作的很好,可是畢竟認識 .opus的手機app不多.故想透過ffmpeg進行變换封裝格式但不重新編碼(recode),避免因recode音質再次變差
ffmepg -i a.opus -codec copy a.ogg可以完美的運行,可是就必須分成兩個動作.
動作一:透過youtube-dl抓取opus音樂並存入硬碟.
動作二:透過ffmpeg至硬碟抓取.opus檔進行轉檔成.ogg
在linux or Dos shell下有一些很好用的指令,例如pipe,下面的例子是利用
youtube-dl將影片抓取後直接(不需存到硬碟)透過pipe以及stdin, stdout將輸出餵給vlc做播放動作
youtube-dl -o - "$url" ¦ vic -
所以我也想透過pipe以及stdin, stdout的方式,重寫shell script.
方法一:(不成功).
youtube-dl -f bestaudio ${1} -o - ¦ ffmpeg -i pipe:0 -codec copy pipe:1 ¦ cat > out.ogg
錯誤訊息: fixed output name but more than one file to download.
方法二:(不成功).
youtube-dl -f bestaudio ${1} -o - ¦ ffmpeg -i pipe:0 -codec copy pipe:1
錯誤訊息: fixed output name but more than one file to download.
看起來無論指不指定輸出檔都會得到相同的錯誤,也分段測試過 ¦前的動作確定只會抓一個檔案
測試了很久始終無法解決,在此請教熟悉linux shell command的前輩能提點一二,感謝!
s912a42 wrote:
亂寫了一下 僅供參考...(恕刪)
感謝大大熱心解答
小弟我參考大大的寫法,也試出來了,以下是我的寫法,非常感謝
title=$(youtube-dl -s -e ${1})
youtube-dl -f bestaudio ${1} -o - --exec 'ffmpeg -i - -codec copy pipe:1' > ${title}.ogg
剛剛測試下載 playlist, 發現會有問題
可以請大大測試一下這個playlist嗎? 再次感謝
https://www.youtube.com/playlist?list=PLlSd7HE5NvCUH5f1NxkFarm9p0gqYJIon
jocoliu wrote:
感謝大大熱心解答小弟...(恕刪)
大概知道問題在哪裡了
name=$(youtube-dl --get-title "$url")
若是一次抓取一個檔案,上面的指令沒有問題,可是若是一次抓取一個清單的歌(>1首歌), 此時 name 的內容就出現問題了
看來應該是無解了, 只好採取兩段方式處理了, 先抓取 *.webm(codec:opus), 再用 ffmpeg 變更容器為 ogg(codec:opus), 這樣就能一次抓取整個歌單的歌, anyway, 還是非常感謝 S912a42 大大的熱心解答
以下是小弟我最後的寫法
youtube-dl -f bestaudio -o '%(title)s.%(ext)s' ${1}
for file in *.webm; do ffmpeg -i "$file" -codec copy "${file%%.webm*}".ogg; done && rm *.webm
程式說明, 利用 youtube-dl 在android 手機模擬終端下執行, 下載 youtube 上的高音質格式音樂(webm with 160kbps opus codec ), 並直接轉換容器格式為普遍較普遍的 ogg, 音樂未重新編碼, 所以不會再有轉換損失。
使用方式
bash 此程式.sh youtube網址 start_no
start_no 是在下載歌曲清單的所有歌曲時,可以指定從第幾首歌開始下載, 不指定時會自動從第一首歌開始下載
#!/bin/bash
declare -i n
clear #clear screen
n=0 #set counter to 0
start=${2} #get the second parameter from user
test -z $start && start=1 # if the 2nd parameter is empty then set it to 1, you can ignore the 2nd parameter if you want start from 1
echo '============= start from '$((start))
while read TITLE
do
n=n+1
name[$((n))]="$TITLE"
echo fetching title "#$((n)) ${name[$((n))]}"
done < <(youtube-dl -s -e -i --playlist-start $((start)) ${1} | sed 's/[@\&\|\^\<\>\?\%\#\{\}\$\*\,\;\"\/]/-/g') #remove special characters
# get song's titles and put them into array name[]
n=0 #reset counter
youtube-dl -s -f bestaudio -g -i --playlist-start $((start)) ${1} | while read URLS #fetch url & read one by one
do
n=n+1
youtube-dl -f bestaudio -i --quiet "$URLS" --buffer-size 16k --http-chunk-size 10M -o - | ffmpeg -loglevel error -y -i - -codec copy -f ogg -ignore_unknown /data/data/com.termux/files/home/storage/exsd/"${name[$((n))]}".ogg && echo "====== #$((n)) ${name[$((n))]} was downloaded ======" || rm /data/data/com.termux/files/home/storage/exsd/"${name[$((n))]}".ogg
#fetch songs & change container to ogg from webm by ffmpeg without changing opus codec, removed it if fetching failed(ex. mp4a) or you will get an ogg file with empty content
done
exit 0
jocoliu wrote:
問題終於解決了, 用...(恕刪)
再次小更新, 5F 的程式在遇到音樂格式是以 m4a 方式儲存時會自動忽略下載並移除不正確的檔案, 更新版則在遇到 bestaudio 僅為 m4a 格式時, 則下載 m4a 格式, 不再經由 ffmpeg 進行 container 變更
#!/bin/bash
declare -i n
clear #clear screen
n=0 #set counter to 0
start=${2} #get the second parameter from user
test -z $start && start=1 # if the 2nd parameter is empty then set it to 1, you can ignore the 2nd parameter if you want start from 1
echo '============= start from '$((start))
while read TITLE
do
n=n+1
name[$((n))]="$TITLE"
echo fetching title "#$((n)) ${name[$((n))]}"
done < <(youtube-dl -s -e -i --playlist-start $((start)) ${1} | sed 's/[@\&\|\^\<\>\?\%\#\{\}\$\*\,\;\"\/]/-/g') #remove special characters
# get song's titles and put them into array name[]
n=0 #reset counter
youtube-dl -s -f bestaudio -g -i --playlist-start $((start)) ${1} | while read URLS #fetch url & read one by one
do
n=n+1
youtube-dl -f bestaudio -i --quiet "$URLS" --buffer-size 16k --http-chunk-size 10M -o - | ffmpeg -loglevel quiet -y -i - -codec copy -f ogg -ignore_unknown /data/data/com.termux/files/home/storage/exsd/"${name[$((n))]}".ogg && echo "====== #$((n)) ${name[$((n))]}.ogg was downloaded ======" || ( rm /data/data/com.termux/files/home/storage/exsd/"${name[$((n))]}".ogg; youtube-dl -f bestaudio -i --quiet "$URLS" --buffer-size 16k --http-chunk-size 10M -o /data/data/com.termux/files/home/storage/exsd/"${name[$((n))]}".m4a; echo "====== #$((n)) ${name[$((n))]}.m4a was downloaded ======" )
#fetch songs & change container to ogg from webm by ffmpeg without changing opus codec, remove the incorrect ogg file and redownload the song with m4a format if fetching failed(ex. if the bestaudio is m4a)
done
exit 0
jocoliu wrote:
再次小更新, 5F ...(恕刪)
再次更新, 前一版本總共會 query youtube 三次, 此次更新減少 query 次數, 加快下載速度
另外加上 time code 及 總共花費時間
#!/bin/bash
declare -i start
declare -i n
declare -i j
declare -i k
clear #clear screen
n=0 #set counter to 0
start=${2} #get the second parameter from user
[ $((start)) == 0 ] && start=1 # if the 2nd parameter is empty then set it to 1, you can ignore the 2nd parameter if you want start from 1
echo $(date) | awk '{printf $4}'
echo " ====== start from #$((start))"
now=$SECONDS
while read TITLE
do
n=n+1
name[$n]="$TITLE"
#get url, title and put them into array
[ $(($n*2/2)) == $(($(($n/2))*2)) ] && ( echo $(date) | awk '{printf $4}'; echo " fetching title #$((($n-2)/2+start)) ${name[$n]%-*} )" )
done < <(youtube-dl -s -f bestaudio --get-url --get-filename -i --playlist-start $((start)) ${1})
echo ======= start to download music files ======
for (( i=1; i<=n; i=i+1))
do
if [ $((i*2/2)) == $(($((i/2))*2)) ]; then
j=j+1
title[$j]="${name[i]}" #name[even] is title
t=${title[$j]}
ext="${t##*.}" #get file extension
t="${t%-*}" #get file name but remove id
if [ $ext == "webm" ]; then
youtube-dl -f bestaudio -i --quiet "${url[$k]}" --buffer-size 16k --http-chunk-size 10M -o - | ffmpeg -loglevel quiet -y -i - -codec copy -f ogg -ignore_unknown /data/data/com.termux/files/home/storage/exsd/"$t".ogg && ( echo $(date) | awk '{printf $4}'; echo " #$((k-1+start)) ===== $t.ogg downloaded" )
else
youtube-dl -f bestaudio -i --quiet "${url[$k]}" --buffer-size 16k --http-chunk-size 10M -o /data/data/com.termux/files/home/storage/exsd/"$t".m4a && ( echo $(date) | awk '{printf $4}'; echo " #$((k-1+start)) ===== $t.m4a downloaded" )
fi
else
k=k+1
url[$k]="${name[i]}" #name[odd] is url
fi
done
echo spent $(( SECONDS - now )) seconds
exit 0
jocoliu wrote:
再次更新, 前一版本...(恕刪)
假日再一發, 持續修改流程, 此版速度比上一版再加快約 25%
#!/bin/bash
declare -i start
declare -i n
clear #clear screen
n=0 #set counter to 0
start=${2} #get the second parameter from user
[ $((start)) == 0 ] && start=1 # if the 2nd parameter is empty then set it to 1, you can ignore the 2nd parameter if you want start from 1
echo $(date) | awk '{printf $4}'
echo " ====== download from #$((start)) "
echo -e downloading ......
now=$SECONDS
while read data
do
n=n+1
name[$n]="$data"
#get id, url, title and put them into array
if [ $((n%3)) == 1 ]; then
id=${name[$n]}
elif [ $((n%3)) == 2 ]; then
url=${name[$n]}
else
title=${name[$n]/-$id/} #get title but remove id
ext="${title##*.}" #get ext
title=${title/.$ext/} #remove ext from title
if [ $ext == "webm" ]; then
youtube-dl -f bestaudio -i --quiet $url --buffer-size 16k --http-chunk-size 10M -o - | ffmpeg -loglevel quiet -y -i - -codec copy -f ogg -ignore_unknown /data/data/com.termux/files/home/storage/exsd/"$title".ogg && ( echo $(date) | awk '{printf $4}'; echo " #$((n/3+start-1)) $title.ogg was downloaded" )
else
youtube-dl -f bestaudio -i --quiet $url --buffer-size 16k --http-chunk-size 10M -o /data/data/com.termux/files/home/storage/exsd/"$title".m4a && ( echo $(date) | awk '{printf $4}'; echo " #$((n/3-1+start)) $title.m4a was downloaded" )
fi
fi
done < <(youtube-dl -s -f bestaudio --get-id --get-url --get-filename -i --playlist-start $((start)) ${1})
echo spent $(( SECONDS - now )) seconds
exit 0
關閉廣告