Amarok 2可以使用ID3v2标签的“ bpm”字段搜索音乐收藏。这将是非常不错的重新标记整个音乐收藏,我能找到我喜欢这首歌的“心情”。
但是,我还没有找到任何可以帮助我的节拍检测软件。你曾经用过吗?CLI,最好。我也很感兴趣是否有相同的标记“ bpm”字段的FLAC。
谢谢!:)
PS我知道这里有一个不错的心情栏功能,但是它对搜索毫无用处。
Amarok 2可以使用ID3v2标签的“ bpm”字段搜索音乐收藏。这将是非常不错的重新标记整个音乐收藏,我能找到我喜欢这首歌的“心情”。
但是,我还没有找到任何可以帮助我的节拍检测软件。你曾经用过吗?CLI,最好。我也很感兴趣是否有相同的标记“ bpm”字段的FLAC。
谢谢!:)
PS我知道这里有一个不错的心情栏功能,但是它对搜索毫无用处。
Answers:
在站点DaveParillo建议我找到了BpmDj项目。它有一个bpmcount
可执行文件,可以很好地计算bpm:它可以处理mp3和flac:
161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
63.5645 Doom3.mp3
剩下的唯一事情就是重新标记集合。每当我成功时,我都会更新此答案。谢谢!:)
步骤1
bpmcount
针对整个集合运行并将结果存储到文本文件中。问题在于,它bpmcount
不时崩溃,并在处理多个文件时尝试占用多达2GB的内存,因此我们应该逐一提供文件名。像这样:
musicdir='/home/ootync/music'
find "$musicdir" -iregex ".*\.\(mp3\|ogg\|flac\|ape\)" -exec bpmcount {} \; \
| fgrep "$musicdir" > "$musicdir/BPMs.txt"
第2步
我们需要一些其他软件包:apt-get install vorbis-tools flac python-mutagen
。现在看看如何添加'bpm'标签:
mid3v2 --TBPM 100 doom3.mp3
vorbiscomment -a -t "BPM=100" mother.ogg
metaflac --set-tag="BPM=100" metallica.flac
las,我没有* .ape曲目
现在我们有了BPM,应该重新标记整个集合。这是脚本:
cat "$musicdir/BPMs.txt" | while read bpm file ; do
bpm=`printf "%.0f" "$bpm"` ;
case "$file" in
*.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
*.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
*.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
esac
done
重新访问步骤2.1 这是一个将BPM标签添加到您的收藏夹的脚本。
每个CPU内核运行一个进程,以加快进程速度。此外,它不使用临时文件,并且能够检测文件是否已被标记。
此外,我发现FLAC有时同时具有ID3和VorbisComment。此脚本会同时更新两者。
#!/bin/bash
function display_help() {
cat <<-HELP
Recursive BPM-writer for multicore CPUs.
It analyzes BPMs of every media file and writes a correct tag there.
Usage: $(basename "$0") path [...]
HELP
exit 0
}
[ $# -lt 1 ] && display_help
#=== Requirements
requires="bpmcount mid3v2 vorbiscomment metaflac"
which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }
#=== Functions
function bpm_read(){
local file="$1"
local ext="${file##*.}"
declare -l ext
# Detect
{ case "$ext" in
'mp3') mid3v2 -l "$file" ;;
'ogg') vorbiscomment -l "$file" ;;
'flac') metaflac --export-tags-to=- "$file" ;;
esac ; } | fgrep 'BPM=' | cut -d'=' -f2
}
function bpm_write(){
local file="$1"
local bpm="${2%%.*}"
local ext="${file##*.}"
declare -l ext
echo "BPM=$bpm @$file"
# Write
case "$ext" in
'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
'flac') metaflac --set-tag="BPM=$bpm" "$file"
mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
;;
esac
}
#=== Process
function oneThread(){
local file="$1"
#=== Check whether there's an existing BPM
local bpm=$(bpm_read "$file")
[ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
#=== Detect a new BPM
# Detect a new bpm
local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
[ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
# Write it
bpm_write "$file" "${bpm%%.*}" >/dev/null
}
NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
find $@ -type f -regextype posix-awk -iregex '.*\.(mp3|ogg|flac)' \
| while read file ; do
[ `jobs -p | wc -l` -ge $NUMCPU ] && wait
echo "$file"
oneThread "$file" &
done
请享用!:)
TBPM
这是一个命令行工具,用于检测BPM并将其放在FLAC文件标签中:
我使用了kolypto的原始脚本,bpmcount
并将其重写为bpm-tag
(的实用性bpm-tools
),我在安装时比较幸运。我也做了一些改进。
您可以在GitHub https://github.com/meridius/bpmwrap上找到它
虽然它不只是您说要寻找的工具,但Banshee媒体播放器可以检测bpm。
我使用Banshee进行所有音乐播放,组织和与便携式播放器同步。我没有隶属关系,但是我尽力尝试了该程序。它还可以根据曲目的各种属性(包括bpm)生成“智能播放列表”。
有一个扩展程序可以分析有关歌曲的各种内容,并找到与您正在播放的歌曲相似的歌曲。它叫做Mirage,我使用了一段时间,但是我不再使用了,因为我创建了许多适合各种心情的播放列表(根据Mirage不一定类似)。
我不知道Banshee是否会将检测到的bpm保存回文件的ID3v2“ bpm”标记中。如果有人知道如何从程序外部轻松检查bpm标记,我将进行检查。
它不是Linux,但可以在Wine中很好地工作-我使用MixMeister BPM Analyzer
我找到了另一种使用正确的BPM值标记MP3文件的工具。
它称为BPMDetect。开源。QT库在Gnome下运行良好。带有GUI,但可以编译为仅控制台版本(如readme.txt中所述,运行“ scons console = 1”)。
否则,最后,我也使用了BpmDJ的“ bpmcount”,因为我很难在64位Ubuntu主机上编译BPMDetect(由于fmodex依赖性)。因此,我采用了上面的(非常酷且写得很好)的shell脚本(请参见下文),它是从BpmDJ网站上提供的[x64 .rpm] [3]中提取的“ bpmcount”二进制文件(我刚刚提取了.rpm与
pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv
它就像一种魅力。我只需要修改上面的脚本,因为它开箱即用,对我而言不起作用(bpmcount二进制文件的stdout / stderr问题)。我的修改是关于文件重定向的:
local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)
这个问题有关stackoverflow的另一个推荐工具是aubio,它与python模块一起提供。
我没有尝试过,因为我有点忙于编译BpmDj。万一其他人在尝试时遇到类似的麻烦,我强烈建议您绝对确定:
随着最新的g ++编译器升级,似乎出现了一些问题,尤其是有关最新的debian和ubuntu版本。当他意识到这些问题后,作者便乐于解决新出现的不兼容问题,并提出了一个新版本,该版本现在可以像魅力一样进行编译。因此,最近因无休止的编译错误而陷入绝望的任何人:您现在就救了。
@ mmx,您的工具看起来也不错,但是它们依赖SoX
,默认情况下没有mp3功能。因此,他们要求首先使用Lame / MAD支持编译SoX,但不幸的是,对于像我这样的懒人来说,这是太多的工作。
要在Mac上使用@meridius的解决方案,我必须做一些额外的工作,并稍微修改一下脚本:
# Let's install bpm-tools
git clone http://www.pogo.org.uk/~mark/bpm-tools.git
cd bpm-tools
make && make install
# There will be errors, but they did not affect the result
# The following three lines could be replaced by including this directory in your $PATH
ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
cd ..
# Time to install a bunch of GNU tools
# Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
brew tap homebrew/dupes; brew install grep
# Now for Mutagen (contains mid3v2)
git clone https://github.com/nex3/mutagen.git
cd mutagen
./setup.py build
sudo ./setup.py install
# There will be errors, but they did not affect the result
cd ..
然后,我不得不修改脚本以指向所有内容的GNU版本,以及其他一些调整:
#!/usr/local/bin/bash
# ================================= FUNCTIONS =================================
function help() {
less <<< 'BPMWRAP
Description:
This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
audio tagging utilities. The purpose is to make BPM (beats per minute)
tagging as easy as possible.
Default behaviour is to look through working directory for *.mp3 files
and compute and print their BPM in the following manner:
[current (if any)] [computed] [filename]
Usage:
bpmwrap [options] [directory or filenames]
Options:
You can specify files to process by one of these ways:
1) state files and/or directories containing them after options
2) specify --import file
3) specify --input file
With either way you still can filter the resulting list using --type option(s).
Remember that the script will process only mp3 files by default, unless
specified otherwise!
-i, --import file
Use this option to set BPM tag for all files in given file instead of
computing it. Expected format of every row is BPM number and absolute path
to filename separated by semicolon like so:
145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
Remember to use --write option too.
-n, --input file
Use this option to give the script list of FILES to process INSTEAD of paths
where to look for them. Each row whould have one absolute path.
This will bypass the searching part and is that way useful when you want
to process large number of files several times. Like when you are not yet
sure what BPM limits to set. Extension filtering will still work.
-o, --output file
Save output also to a file.
-l, --list-save file
Save list of files about to get processed. You can use this list later
as a file for --input option.
-t, --type filetype
Extension of file type to work with. Defaults to mp3. Can be specified
multiple times for more filetypes. Currently supported are mp3 ogg flac.
-e, --existing-only
Only show BPM for files that have it. Do NOT compute new one.
-w, --write
Write computed BPM to audio file but do NOT overwrite existing value.
-f, --force
Write computed BPM to audio file even if it already has one. Aplicable only
with --write option.
-m, --min minbpm
Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
-x, --max maxbpm
Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
-v, --verbose
Show "progress" messages.
-c, --csv-friendly
Use semicolon (;) instead of space to separate output columns.
-h, --help
Show this help.
Note:
Program bpm-tag (on whis is this script based) is looking only for lowercase
file extensions. If you get 0 (zero) BPM, this should be the case. So just
rename the file.
License:
GPL V2
Links:
bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)
Dependencies:
bpm-tag mid3v2 vorbiscomment metaflac
Author:
Martin Lukeš (martin.meridius@gmail.com)
Based on work of kolypto (http://superuser.com/a/129157/137326)
'
}
# Usage: result=$(inArray $needle haystack[@])
# @param string needle
# @param array haystack
# @returns int (1 = NOT / 0 = IS) in array
function inArray() {
needle="$1"
haystack=("${!2}")
out=1
for e in "${haystack[@]}" ; do
if [[ "$e" = "$needle" ]] ; then
out=0
break
fi
done
echo $out
}
# Usage: result=$(implode $separator array[@])
# @param char separator
# @param array array to implode
# @returns string separated array elements
function implode() {
separator="$1"
array=("${!2}")
IFSORIG=$IFS
IFS="$separator"
echo "${array[*]}"
IFS=$IFSORIG
}
# @param string file
# @returns int BPM value
function getBpm() {
local file="$1"
local ext="${file##*.}"
declare -l ext # convert to lowercase
{ case "$ext" in
'mp3') mid3v2 -l "$file" ;;
'ogg') vorbiscomment -l "$file" ;;
'flac') metaflac --export-tags-to=- "$file" ;;
esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
}
# @param string file
# @param int BPM value
function setBpm() {
local file="$1"
local bpm="${2%%.*}"
local ext="${file##*.}"
declare -l ext # convert to lowercase
case "$ext" in
'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
'flac') metaflac --set-tag="BPM=$bpm" "$file"
mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
;;
esac
}
# # @param string file
# # @returns int BPM value
function computeBpm() {
local file="$1"
local m_opt=""
[ ! -z "$m" ] && m_opt="-m $m"
local x_opt=""
[ ! -z "$x" ] && x_opt="-x $x"
local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
echo $(echo "$row" \
| gsed -r 's/.+ ([0-9]+\.[0-9]{3}) BPM/\1/' \
| gawk '{printf("%.0f\n", $1)}')
}
# @param string file
# @param int file number
# @param int BPM from file list given by --import option
function oneThread() {
local file="$1"
local filenumber="$2"
local bpm_hard="$3"
local bpm_old=$(getBpm "$file")
[ -z "$bpm_old" ] && bpm_old="NONE"
if [ "$e" ] ; then # only show existing
myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
else # compute new one
if [ "$bpm_hard" ] ; then
local bpm_new="$bpm_hard"
else
local bpm_new=$(computeBpm "$file")
fi
[ "$w" ] && { # write new one
if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
setBpm "$file" "$bpm_new"
else
[ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
fi
}
myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
fi
}
function myEcho() {
[ "$o" ] && echo -e "$1" >> "$o"
echo -e "$1"
}
# ================================== OPTIONS ==================================
eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch" \
-l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")
declare i n o l t e w f m x v c h
declare -a INPUTFILES
declare -a INPUTTYPES
while [ $# -gt 0 ] ; do
case "$1" in
-i|--import) shift ; i="$1" ; shift ;;
-n|--input) shift ; n="$1" ; shift ;;
-o|--output) shift ; o="$1" ; shift ;;
-l|--list-save) shift ; l="$1" ; shift ;;
-t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
-e|--existing-only) e=1 ; shift ;;
-w|--write) w=1 ; shift ;;
-f|--force) f=1 ; shift ;;
-m|--min) shift ; m="$1" ; shift ;;
-x|--max) shift ; x="$1" ; shift ;;
-v|--verbose) v=1 ; shift ;;
-c|--csv-friendly) c=1 ; shift ;;
-h|--help) h=1 ; shift ;;
--) shift ;;
-*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
*) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
esac
done
# ================================= DEFAULTS ==================================
#NOTE Remove what requisities you don't need but don't try to use them after!
# always mp3/flac ogg flac
REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }
[ "$h" ] && {
help
exit 0
}
[[ $m && $x && ( $m -ge $x ) ]] && {
myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
exit 1
}
[[ "$i" && "$n" ]] && {
echo "You cannot specify both -i and -n options!"
exit 1
}
[[ "$i" && ( "$m" || "$x" ) ]] && {
echo "You cannot use -m nor -x option with -i option!"
exit 1
}
[ "$e" ] && {
[[ "$w" || "$f" ]] && {
echo "With -e option you don't have any value to write!"
exit 1
}
[[ "$m" || "$x" ]] && {
echo "With -e option you don't have any value to count!"
exit 1
}
}
for file in "$o" "$l" ; do
if [ -f "$file" ] ; then
while true ; do
read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
case "$key" in
y|Y|"") echo "" > "$file" ; break ;;
n|N) exit 0 ;;
esac
echo ""
done
echo ""
fi
done
[ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")
# NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/\2/')"
LASTPID=0
TYPESALLOWED=("mp3" "ogg" "flac")
# declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
for type in "${INPUTTYPES[@]}" ; do
[[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
exit 1
}
done
### here are three ways how to pass files to the script...
if [ "$i" ] ; then # just parse given file list and set BPM to listed files
if [ -f "$i" ] ; then
# myEcho "Setting BPM tags from given file ..."
while read row ; do
bpm="${row%%;*}"
file="${row#*;}"
ext="${file##*.}"
ext="${ext,,}" # convert to lowercase
if [ -f "$file" ] ; then
if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
FILES=("${FILES[@]}" "$file")
BPMIMPORT["$file"]="$bpm"
else
myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
fi
else
myEcho "Skipping non-existing file $file"
fi
done < "$i"
else
myEcho "Given import file does not exists!"
exit 1
fi
elif [ "$n" ] ; then # get files from file list
if [ -f "$n" ] ; then
rownumber=1
while read file ; do
if [ -f "$file" ] ; then
ext="${file##*.}"
ext="${ext,,}" # convert to lowercase
if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
FILES=("${FILES[@]}" "$file")
else
myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
fi
else
myEcho "Skipping file on row $rownumber (non-existing) ... $file"
fi
let rownumber++
done < "$n"
unset rownumber
else
myEcho "Given input file $n does not exists!"
exit 1
fi
else # get files from given parameters
[ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
for file in "${INPUTFILES[@]}" ; do
[ ! -e "$file" ] && {
myEcho "File or directory $file does not exist!"
exit 1
}
done
impl_types=`implode "|" INPUTTYPES[@]`
while read file ; do
echo -ne "Creating list of files ... (${#FILES[@]}) ${file}\033[0K"\\r
FILES=("${FILES[@]}" "$file")
done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*\.($impl_types)")
echo -e "Counted ${#FILES[@]} files\033[0K"\\r
fi
[ "$l" ] && printf '%s\n' "${FILES[@]}" > "$l"
NUMFILES=${#FILES[@]}
FILENUMBER=1
[ $NUMFILES -eq 0 ] && {
myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
exit 1
}
declare SEP=" "
[ "$c" ] && SEP=";"
# =============================== MAIN SECTION ================================
if [ "$e" ] ; then # what heading to show
myEcho "num${SEP}old${SEP}filename"
else
myEcho "num${SEP}old${SEP}new${SEP}filename"
fi
for file in "${FILES[@]}" ; do
[ `jobs -p | wc -l` -ge $NUMCPU ] && wait
[ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})\t$file ..."
oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
LASTPID="$!"
let FILENUMBER++
done
[ "$v" ] && myEcho "Waiting for last process ..."
wait $LASTPID
[ "$v" ] && myEcho \\n"DONE"
感谢您的辛勤工作@kolypto和@meridius。
...我为维护CLI工作流程而付出的辛苦,不花钱购买音乐工具...