2335 lines
48 KiB
Bash
Executable File
2335 lines
48 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
## Define exit codes
|
|
# General config errors [10-19]
|
|
ELOAD=10
|
|
EINTERVAL=11
|
|
ENOCFG=19
|
|
# Source cofig errors [20-29]
|
|
# Destination config errors [30-49]
|
|
EFORMAT=30
|
|
ECHANNEL=31
|
|
EFMTINVPARM=49
|
|
|
|
# config structures
|
|
declare -A \
|
|
destinationchannels \
|
|
destinationfat32compat \
|
|
destinationcopymime \
|
|
destinationformat \
|
|
destinationfrequency \
|
|
destinationid \
|
|
destinationloss \
|
|
destinationmaxbps \
|
|
destinationnormalize \
|
|
destinationpath \
|
|
destinationquality \
|
|
destinationrename \
|
|
destinationnoresample \
|
|
destinationrenamepath \
|
|
destinationskipmime \
|
|
|| {
|
|
echo "Check your Bash version. You need >= 4.0" >&2
|
|
exit $EBASHVERS
|
|
}
|
|
|
|
declare -r \
|
|
DOCDIR=./doc \
|
|
SHAREDIR=./share
|
|
declare -r \
|
|
exampleconf=$DOCDIR/example.cfg \
|
|
schema=$SHAREDIR/schema.sql \
|
|
\
|
|
oldIFS="$IFS"
|
|
|
|
source $SHAREDIR/id3genres
|
|
|
|
shopt -s extglob
|
|
|
|
#parse arguments
|
|
OPTERR=0
|
|
while getopts ':c:l:T:F:hD' opt
|
|
do
|
|
case $opt in
|
|
c)
|
|
cffile="$OPTARG"
|
|
;;
|
|
l)
|
|
cliload="$OPTARG"
|
|
;;
|
|
T)
|
|
cliltimer="$OPTARG"
|
|
;;
|
|
F)
|
|
forceall+=("$OPTARG")
|
|
;;
|
|
h)
|
|
help
|
|
exit 0
|
|
;;
|
|
D)
|
|
(( debug++ ))
|
|
;;
|
|
:)
|
|
echo "-$OPTARG requires an argument"
|
|
help
|
|
exit 127
|
|
;;
|
|
*)
|
|
echo "Unrecognized option: -$OPTARG"
|
|
help
|
|
exit 127
|
|
;;
|
|
esac
|
|
done
|
|
|
|
#parse config
|
|
getConfigGeneral() {
|
|
case $key in
|
|
'max-load')
|
|
expr='^[0-9]*$'
|
|
if [[ $value =~ $expr ]]
|
|
then
|
|
maxload="$value"
|
|
else
|
|
echo "Invalid max-load value: $value" >&2
|
|
exit $ELOAD
|
|
fi
|
|
unset expr
|
|
;;
|
|
'load-interval')
|
|
expr='^[0-9]*$'
|
|
if [[ $value =~ $expr ]]
|
|
then
|
|
loadinterval="$value"
|
|
else
|
|
echo "Invalid load-interval value: $value" >&2
|
|
exit $EINTERVAL
|
|
fi
|
|
unset expr
|
|
;;
|
|
'temporary-directory')
|
|
tempdir="$value"
|
|
;;
|
|
'database')
|
|
database="$value"
|
|
;;
|
|
debug)
|
|
(( value > debug )) && debug=$value
|
|
;;
|
|
esac
|
|
}
|
|
|
|
getConfigSource() {
|
|
case "$key" in
|
|
'path')
|
|
sourcepath="$value"
|
|
;;
|
|
'skip')
|
|
skippeddirectories+=( "$value" )
|
|
;;
|
|
esac
|
|
}
|
|
|
|
getConfigDestination() {
|
|
case "$key" in
|
|
'path')
|
|
destinationpath["$destination"]="$value"
|
|
;;
|
|
'format')
|
|
case "$value" in
|
|
'mp3')
|
|
destinationformat["$destination"]=mp3
|
|
;;
|
|
'opus')
|
|
destinationformat["$destination"]=opus
|
|
;;
|
|
'vorbis')
|
|
destinationformat["$destination"]=vorbis
|
|
;;
|
|
*)
|
|
echo "Unsupported destination format: $value" >2&
|
|
exit $EFORMAT
|
|
;;
|
|
esac
|
|
;;
|
|
'quality')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid quality value: $value" >&2
|
|
exit $EQUALITY
|
|
fi
|
|
unset expr
|
|
case "${destinationformat["$destination"]}" in
|
|
'vorbis')
|
|
destinationquality["$destination"]="$value"
|
|
;;
|
|
*)
|
|
echo "Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
|
exit $EFMTINVPARM
|
|
;;
|
|
esac
|
|
;;
|
|
'normalize')
|
|
case $value in
|
|
'true'|'on'|'yes')
|
|
destinationnormalize["$destination"]=1
|
|
;;
|
|
'false'|'off'|'no')
|
|
destinationnormalize["$destination"]=0
|
|
;;
|
|
*)
|
|
echo "normalize takes values:" \
|
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
|
"'off'"
|
|
;;
|
|
esac
|
|
;;
|
|
'bitrate')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid bitrate value: $value" >&2
|
|
exit $EQUALITY
|
|
fi
|
|
unset expr
|
|
case "${destinationformat["$destination"]}" in
|
|
'opus')
|
|
destinationquality["$destination"]="$value"
|
|
;;
|
|
'mp3')
|
|
destinationquality["$destination"]="$value"
|
|
;;
|
|
*)
|
|
echo "$Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
|
exit $EFMTINVPARM
|
|
;;
|
|
esac
|
|
;;
|
|
'loss')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid loss value: $value" >&2
|
|
exit $EQUALITY
|
|
fi
|
|
unset expr
|
|
case "${destinationformat["$destination"]}" in
|
|
'opus')
|
|
destinationloss["$destination"]="$value"
|
|
;;
|
|
*)
|
|
echo "$Invalid parameter \"$key\" for format \"${destinationformat["$destination"]}\"" >&2
|
|
exit $EFMTINVPARM
|
|
;;
|
|
esac
|
|
;;
|
|
'channels')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid channel count: $value" >&2
|
|
exit $ECHANNEL
|
|
fi
|
|
unset expr
|
|
destinationchannels["$destination"]=$value
|
|
;;
|
|
'frequency')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid frequency value: $value" >&2
|
|
exit $ECHANNEL
|
|
fi
|
|
unset expr
|
|
destinationfrequency["$destination"]=$value
|
|
;;
|
|
'noresample')
|
|
case $value in
|
|
'true'|'on'|'yes')
|
|
destinationnoresample["$destination"]=1
|
|
;;
|
|
'false'|'off'|'no')
|
|
destinationnoresample["$destination"]=0
|
|
;;
|
|
*)
|
|
echo "noresample takes values:" \
|
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
|
"'off'"
|
|
;;
|
|
esac
|
|
;;
|
|
'rename')
|
|
case "$value" in
|
|
*/*)
|
|
destinationrenamepath["$destination"]="${value%/*}"
|
|
;;
|
|
esac
|
|
destinationrename["$destination"]="${value##*/}"
|
|
;;
|
|
'fat32compat')
|
|
case $value in
|
|
'true'|'on'|'yes')
|
|
destinationfat32compat["$destination"]=1
|
|
;;
|
|
'false'|'off'|'no')
|
|
destinationfat32compat["$destination"]=0
|
|
;;
|
|
*)
|
|
echo "fat32compat takes values:" \
|
|
"'yes' ,'true' ,'on', 'no', 'false',"\
|
|
"'off'"
|
|
;;
|
|
esac
|
|
;;
|
|
'skip_mime-type')
|
|
destinationskipmime[$destination]="${destinationskipmime[$destination]:+${destinationskipmime[$destination]}|}$value"
|
|
;;
|
|
'copy_mime-type')
|
|
destinationcopymime[$destination]="${destinationcopymime[$destination]:+${destinationcopymime[$destination]}|}$value"
|
|
;;
|
|
'higher-than')
|
|
expr='^[0-9]*$'
|
|
if ! [[ $value =~ $expr ]]
|
|
then
|
|
echo "Invalid higher-than bitrate value: $value" >&2
|
|
exit $EMAXBPS
|
|
fi
|
|
unset expr
|
|
destinationmaxbps[$destination]="$value"
|
|
;;
|
|
esac
|
|
}
|
|
getConfig() {
|
|
while read key value
|
|
do
|
|
case $key in
|
|
'#'*)
|
|
#comment
|
|
;;
|
|
'')
|
|
#empty line
|
|
;;
|
|
'[general]')
|
|
context=General
|
|
;;
|
|
'[source]')
|
|
context=Source
|
|
;;
|
|
\[*\])
|
|
context=Destination
|
|
destination="${key#[}"
|
|
destination="${destination%]}"
|
|
;;
|
|
*)
|
|
getConfig$context
|
|
;;
|
|
esac
|
|
done < ~/.atom/atom.cfg
|
|
}
|
|
|
|
#check sanity
|
|
|
|
openDatabase() {
|
|
if [ ! -d "$tempdir" ]
|
|
then
|
|
mkdir -p "$tempdir"
|
|
fi
|
|
rm -f "$tempdir"/sqlite.{in,out}
|
|
mkfifo "$tempdir"/sqlite.{in,out}
|
|
if [ ! -f "$database" ]
|
|
then
|
|
if [ ! -d "${database%/*}" ]
|
|
then
|
|
mkdir -p "${database%/*}"
|
|
fi
|
|
sqlite3 "$database" < $schema
|
|
fi
|
|
sqlite3 -bail "$database" \
|
|
< "$tempdir/sqlite.in" \
|
|
> "$tempdir/sqlite.out" &
|
|
exec 3> "$tempdir"/sqlite.in
|
|
exec 4< "$tempdir"/sqlite.out
|
|
if (( debug > 2 ))
|
|
then
|
|
exec 5>&3
|
|
exec 3> >(tee -a $tempdir/debug.log >&5)
|
|
fi
|
|
echo 'PRAGMA foreign_keys = ON;' >&3
|
|
}
|
|
|
|
closeDatabase() {
|
|
echo .quit >&3
|
|
(( debug )) && echo -n "Waiting for SQLite to terminate... "
|
|
wait
|
|
(( debug )) && echo OK
|
|
exec 3>&-
|
|
exec 4<&-
|
|
rm "$tempdir"/sqlite.{in,out}
|
|
}
|
|
|
|
Select() {
|
|
#Select table [col1 [col2 [..]]] < WHERE_key WHERE_operator WHERE_value
|
|
# [WHERE_key WHERE_operator WHERE_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
col \
|
|
columns \
|
|
operator \
|
|
results \
|
|
where_statement
|
|
shift
|
|
for col
|
|
do
|
|
(( ${#columns} )) && columns+=','
|
|
columns+="$col"
|
|
done
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
|
done
|
|
echo "SELECT IFNULL(" \
|
|
"(SELECT $columns FROM $table" \
|
|
"WHERE ${where_statement[@]})" \
|
|
",'SQL::Select:not found'" \
|
|
");" >&3
|
|
read -u 4 results
|
|
if ! [[ $results == "SQL::Select:not found" ]]
|
|
then
|
|
echo "$results"
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
Insert() {
|
|
#Insert table [no_id] < key value
|
|
# [key value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
no_id="${2:-0}" \
|
|
insert_keys \
|
|
insert_values \
|
|
results
|
|
while read key value
|
|
do
|
|
(( ${#insert_keys} )) && insert_keys+=","
|
|
insert_keys+='`'"$key"'`'
|
|
(( ${#insert_values} )) && insert_values+=","
|
|
case $value in
|
|
'NULL')
|
|
insert_values+="NULL"
|
|
;;
|
|
+([0-9])?(.+([0-9])))
|
|
insert_values+=$value
|
|
;;
|
|
*)
|
|
insert_values+='"'"${value//\"/\"\"}"'"'
|
|
;;
|
|
esac
|
|
done
|
|
echo "INSERT INTO $table" \
|
|
"( $insert_keys )" \
|
|
"VALUES" \
|
|
"( $insert_values );" >&3
|
|
(( no_id )) || {
|
|
echo 'SELECT LAST_INSERT_ROWID();' >&3
|
|
read -u 4 results
|
|
echo "$results"
|
|
}
|
|
}
|
|
Update(){
|
|
#Update table set_key set_value [set_key set_value […]] < where_key where_operator where_value
|
|
# [where_key where_operator where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
key \
|
|
argument \
|
|
operator \
|
|
value \
|
|
set_statement \
|
|
where_keys \
|
|
where_values \
|
|
what \
|
|
where_statement \
|
|
results
|
|
shift
|
|
what=key
|
|
for argument
|
|
do
|
|
case $what in
|
|
key)
|
|
set_statement="${set_statement:+${set_statement},}\`$argument\`"
|
|
what=value
|
|
;;
|
|
value)
|
|
case $argument in
|
|
'NULL')
|
|
set_statement+=" = NULL"
|
|
;;
|
|
+([0-9])?(.+([0-9])))
|
|
set_statement+=" = $argument"
|
|
;;
|
|
*)
|
|
set_statement+=" = "'"'"${argument//\"/\"\"}"'"'
|
|
;;
|
|
esac
|
|
what=key
|
|
;;
|
|
esac
|
|
done
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
case $value in
|
|
'NULL')
|
|
where_statement+=( "$key is NULL" )
|
|
;;
|
|
+([0-9.]))
|
|
where_statement+=( "$key $operator $value" )
|
|
;;
|
|
*)
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
|
;;
|
|
esac
|
|
done
|
|
echo "UPDATE '$table' SET" \
|
|
"$set_statement" \
|
|
"WHERE" \
|
|
"${where_statement[@]}" \
|
|
";" >&3
|
|
}
|
|
InsertIfUnset() {
|
|
#InsertIfUnset table [no_id] < key value \n key value
|
|
local \
|
|
table="$1" \
|
|
no_id="${2:-0}" \
|
|
column \
|
|
key \
|
|
keys \
|
|
results \
|
|
value \
|
|
values
|
|
while read key value
|
|
do
|
|
keys+=( "$key" )
|
|
values+=( "$value" )
|
|
done
|
|
if (( no_id ))
|
|
then
|
|
column="${keys[0]}"
|
|
else
|
|
column='id'
|
|
fi
|
|
if ! results=$(
|
|
Select "$table" "$column" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
then
|
|
results=$(
|
|
Insert "$table" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
fi
|
|
echo "$results"
|
|
}
|
|
InsertOrUpdate() {
|
|
#InsertOrUpdate table set_key set_value [set_key set_value […]] < where_key where_value
|
|
# [where_key where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
argument \
|
|
key \
|
|
keys \
|
|
set_keys \
|
|
set_values \
|
|
value \
|
|
values \
|
|
what \
|
|
results
|
|
shift
|
|
what=key
|
|
for argument
|
|
do
|
|
case $what in
|
|
key)
|
|
set_keys+=( "$argument" )
|
|
what=value
|
|
;;
|
|
value)
|
|
set_values+=( "$argument" )
|
|
what=key
|
|
;;
|
|
esac
|
|
done
|
|
while read key value
|
|
do
|
|
keys+=( "$key" )
|
|
values+=( "$value" )
|
|
done
|
|
if results=$(
|
|
Select "$table" ${keys[0]} < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
then
|
|
Update "$table" "$@" < <(
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" = "${values[$key]}"
|
|
done
|
|
)
|
|
else
|
|
results=$(
|
|
Insert "$table" < <(
|
|
for key in ${!set_keys[@]}
|
|
do
|
|
echo "${set_keys[$key]}" "${set_values[$key]}"
|
|
done
|
|
for key in ${!keys[@]}
|
|
do
|
|
echo "${keys[$key]}" "${values[$key]}"
|
|
done
|
|
)
|
|
)
|
|
fi
|
|
echo "$results"
|
|
}
|
|
Delete() {
|
|
#Delete table < where_key where_operator where_value
|
|
# [where_key where_operator where_value
|
|
# […]]
|
|
local \
|
|
table="$1" \
|
|
key \
|
|
operator \
|
|
value \
|
|
where_statement \
|
|
results
|
|
while read key operator value
|
|
do
|
|
(( ${#where_statement} )) && where_statement+=( "AND" )
|
|
if [[ $value == NULL ]]
|
|
then
|
|
where_statement+=( "$key is NULL" )
|
|
else
|
|
where_statement+=( "$key $operator "'"'"${value//\"/\"\"}"'"' )
|
|
fi
|
|
done
|
|
echo "DELETE from $table WHERE ${where_statement[@]};" >&3
|
|
}
|
|
|
|
createDestinations() {
|
|
for destination in ${!destinationpath[@]}
|
|
do
|
|
if ! [ -d "${destinationpath["$destination"]}" ]
|
|
then
|
|
if ! mkdir -p "${destinationpath["$destination"]}"
|
|
then
|
|
echo "$destination: Could not create ${destinationpath["$destination"]}!"
|
|
exit $EINVDEST
|
|
fi
|
|
fi
|
|
destinationid["$destination"]=$(
|
|
InsertIfUnset destinations <<<"name $destination"
|
|
)
|
|
done
|
|
}
|
|
|
|
progressSpin() {
|
|
case $(( ++count % 40 )) in
|
|
0) echo -ne '\b|' ;;
|
|
10) echo -ne '\b/' ;;
|
|
20) echo -en '\b-' ;;
|
|
30) echo -ne '\b\\' ;;
|
|
*) ;;
|
|
esac
|
|
}
|
|
getFiles() {
|
|
scantime=$(date +%s)
|
|
for prune_expression in "${skippeddirectories[@]}"
|
|
do
|
|
prunes+="-path $sourcepath$prune_expression -prune -o "
|
|
done
|
|
echo -n "Scanning $sourcepath... "
|
|
# We probably have thousands of files, don't waste time on disk writes
|
|
echo 'BEGIN TRANSACTION;' >&3
|
|
while read time size filename
|
|
do
|
|
if ! Select source_files id >/dev/null <<-EOWhere
|
|
filename = $filename
|
|
mime_type > 0
|
|
last_change = $time
|
|
EOWhere
|
|
then
|
|
mimetype=$(file -b --mime-type "$sourcepath/$filename")
|
|
mimetypeid=$(
|
|
InsertIfUnset mime_types <<-EOInsert
|
|
mime_text $mimetype
|
|
EOInsert
|
|
)
|
|
InsertOrUpdate source_files \
|
|
last_change $time \
|
|
size $size \
|
|
last_seen $scantime \
|
|
mime_type $mimetypeid \
|
|
>/dev/null \
|
|
<<-EOWhere
|
|
filename $filename
|
|
EOWhere
|
|
(( ++new ))
|
|
if (( new % 1000 == 0 ))
|
|
then
|
|
echo 'COMMIT;BEGIN TRANSACTION;' >&3
|
|
(( debug )) \
|
|
&& echo -ne "\bCommitted $count files... "
|
|
fi
|
|
else
|
|
Update source_files last_seen $scantime <<-EOWhere
|
|
filename = $filename
|
|
EOWhere
|
|
fi
|
|
progressSpin
|
|
done < <(
|
|
find "$sourcepath" $prunes -type f -printf "%T@ %s %P\n"
|
|
)
|
|
echo 'COMMIT;' >&3
|
|
echo -e "\r${count:-0} files found, ${new:=0} new or changed."
|
|
unset count
|
|
}
|
|
|
|
updateMimes() {
|
|
Update mime_actions action 1 <<<"action != 1"
|
|
for destination in ${!destinationskipmime[@]}
|
|
do
|
|
IFS='|'
|
|
for mime_type in ${destinationskipmime["$destination"]}
|
|
do
|
|
IFS="$oldIFS"
|
|
Update mime_type_actions action 0 >/dev/null < <(
|
|
cat <<-EOWhere
|
|
destination_id = ${destinationid["$destination"]}
|
|
mime_text LIKE ${mime_type//\*/%}
|
|
EOWhere
|
|
)
|
|
done
|
|
done
|
|
for destination in ${!destinationcopymime[@]}
|
|
do
|
|
IFS='|'
|
|
for mime_type in ${destinationcopymime["$destination"]}
|
|
do
|
|
IFS="$oldIFS"
|
|
Update mime_type_actions action 2 >/dev/null < <(
|
|
cat <<-EOWhere
|
|
destination_id = ${destinationid["$destination"]}
|
|
mime_text LIKE ${mime_type//\*/%}
|
|
EOWhere
|
|
)
|
|
done
|
|
done
|
|
}
|
|
|
|
removeObsoleteFiles() {
|
|
Delete source_files <<-EOWhere
|
|
last_seen < $scantime
|
|
EOWhere
|
|
}
|
|
|
|
getRateChannelSoxi() {
|
|
rate=$(soxi -r "$sourcepath/$filename" 2>/dev/null)
|
|
channels=$(soxi -c "$sourcepath/$filename" 2>/dev/null)
|
|
}
|
|
|
|
getRateChannelMPC() {
|
|
while read key value garbage
|
|
do
|
|
case $key in
|
|
'samplerate:')
|
|
rate=$value
|
|
;;
|
|
'channels:')
|
|
channels=$value
|
|
;;
|
|
esac
|
|
done < <(
|
|
mpcdec "$sourcepath/$filename" -i 2>&1
|
|
)
|
|
}
|
|
|
|
gettag() {
|
|
echo -e "$infos" \
|
|
| sed -n "/^${1}=/I{s/^${1}=//I;p;q}"
|
|
}
|
|
|
|
getInfosMP3_version='ID3-2'
|
|
tagreaders+=( "$getInfosMP3_version" )
|
|
getInfos::MP3() {
|
|
tagreader="$getInfosMP3_version"
|
|
infos=$(
|
|
soxi "$sourcepath/$filename" 2>/dev/null \
|
|
| sed 's/ *: /=/'
|
|
)
|
|
album=$(gettag album)
|
|
artist=$(gettag artist)
|
|
genre=$(gettag genre)
|
|
title=$(gettag title)
|
|
tracknum=$(gettag tracknumber)
|
|
year=$(gettag year)
|
|
expr='^\([0-9]*\)$'
|
|
if [[ $genre =~ $expr ]]
|
|
then
|
|
genre=${genre%)}
|
|
genre=${genre#(}
|
|
genre="${id3genres[$genre]}"
|
|
fi
|
|
infos="${infos/: /=}"
|
|
channels=$(gettag channels)
|
|
rate=$(gettag 'sample rate')
|
|
bitrate=$(gettag 'bit rate')
|
|
bitrate=${bitrate%%.*}
|
|
bitrate=${bitrate%k}
|
|
}
|
|
|
|
getInfosOgg_version='Ogg-1'
|
|
tagreaders+=( "$getInfosOgg_version" )
|
|
getInfos::Ogg() {
|
|
tagreader="$getInfosOgg_version"
|
|
infos=$(
|
|
ogginfo "$sourcepath/$filename" \
|
|
| sed 's/\t//'
|
|
)
|
|
albumartist=$(gettag albumartist)
|
|
album=$(gettag album)
|
|
artist=$(gettag artist)
|
|
composer=$(gettag composer)
|
|
disc=$(gettag discnumber)
|
|
genre=$(gettag genre)
|
|
performer=$(gettag performer)
|
|
title=$(gettag title)
|
|
tracknum=$(gettag tracknumber)
|
|
tracktotal=$(gettag tracktotal)
|
|
if [ -n "$tracknum" -a -n "$tracktotal" ]
|
|
then
|
|
tracknum="$tracknum/$tracktotal"
|
|
fi
|
|
year=$(gettag date)
|
|
infos="${infos/: /=}"
|
|
rate=$(gettag rate|head -n1)
|
|
channels=$(gettag channels|head -n1)
|
|
bitrate=$(gettag 'nominal bitrate')
|
|
bitrate=${bitrate%%,*}
|
|
}
|
|
|
|
getInfosFLAC_version='FLAC-1'
|
|
tagreaders+=( "$getInfosFLAC_version" )
|
|
getInfos::FLAC() {
|
|
tagreader="$getInfosFLAC_version"
|
|
infos=$(
|
|
metaflac \
|
|
--show-tag=ALBUM \
|
|
--show-tag=ALBUMARTIST \
|
|
--show-tag=ARTIST \
|
|
--show-tag=COMPOSER \
|
|
--show-tag=DATE \
|
|
--show-tag=DISCNUMBER \
|
|
--show-tag=GENRE \
|
|
--show-tag=PERFORMER \
|
|
--show-tag=TITLE \
|
|
--show-tag=TRACKNUMBER \
|
|
--show-tag=TRACKTOTAL \
|
|
"$sourcepath/$filename"
|
|
)
|
|
albumartist=$(gettag albumartist)
|
|
album=$(gettag album)
|
|
artist=$(gettag artist)
|
|
composer=$(gettag composer)
|
|
disc=$(gettag discnumber)
|
|
genre=$(gettag genre)
|
|
performer=$(gettag performer)
|
|
title=$(gettag title)
|
|
tracknum="$(gettag tracknumber)/$(gettag tracktotal)"
|
|
year=$(gettag date)
|
|
if [ -n "$tracknum" -a -n "$tracktotal" ]
|
|
then
|
|
tracknum="$tracknum/$tracktotal"
|
|
fi
|
|
year=$(gettag DATE)
|
|
{
|
|
read rate
|
|
read channels
|
|
} < <(
|
|
metaflac \
|
|
--show-sample-rate \
|
|
--show-channels \
|
|
"$sourcepath/$filename"
|
|
)
|
|
}
|
|
|
|
getInfosAPE_version='APE-1'
|
|
tagreaders+=( "$getInfosAPE_version" )
|
|
getInfos::APE() {
|
|
# I was not able to find a decent cli tool to read APE tags.
|
|
# This is raw but works for the very few MusePack files I got.
|
|
#
|
|
# Please tell me if you know of any good tool.
|
|
tagreader="$getInfosAPE_version"
|
|
IFS='='
|
|
while read tag value
|
|
do
|
|
IFS="$oldIFS"
|
|
case $tag in
|
|
[Aa][Ll][Bb][Uu][Mm]' '[Aa][Rr][Tt][Ii][Ss][Tt])
|
|
albumartist="$value"
|
|
;;
|
|
[Aa][Rr][Tt][Ii][Ss][Tt])
|
|
artist="$value"
|
|
;;
|
|
[Yy][Ee][Aa][Rr])
|
|
year="$value"
|
|
;;
|
|
[Aa][Ll][Bb][Uu][Mm])
|
|
album="$value"
|
|
;;
|
|
[Tt][Ii][Tt][Ll][Ee])
|
|
title="$value"
|
|
;;
|
|
[Tt][Rr][Aa][Cc][Kk])
|
|
tracknum="$value"
|
|
;;
|
|
[Gg][Ee][Nn][Rr][Ee])
|
|
genre="$value"
|
|
;;
|
|
[Cc][Oo][Mm][Pp][Oo][Ss][Ee][Rr])
|
|
composer="$value"
|
|
;;
|
|
[Pp][Ee][Rr][Ff][Oo][Rr][Mm][Ee][Rr])
|
|
performer="$value"
|
|
;;
|
|
*)
|
|
;;
|
|
esac
|
|
IFS='='
|
|
done < <(
|
|
IFS="$oldIFS"
|
|
sed \
|
|
's/APETAGEX/\n/;s/[\x00\-\x1F]\x00\+/\n/g;s/\x00/=/g' \
|
|
"$sourcepath/$filename" \
|
|
| egrep -i \
|
|
'^(Album Artist|Artist|Year|Album|Title|Track|Genre|Composer|Performer)='
|
|
)
|
|
IFS="$oldIFS"
|
|
}
|
|
|
|
tryAPE() {
|
|
grep -q 'APETAGEX' \
|
|
"$sourcepath/$filename" \
|
|
&& type=APE
|
|
}
|
|
|
|
getTags_version='unknown-1'
|
|
tagreaders+=( "$getTags_version" )
|
|
getTags() {
|
|
unset type
|
|
case "$mimetype" in
|
|
audio/mpeg)
|
|
type=MP3
|
|
;;
|
|
application/ogg)
|
|
type=Ogg
|
|
;;
|
|
audio/x-flac)
|
|
type=FLAC
|
|
;;
|
|
*)
|
|
extendedtype=$(file -b "$sourcepath/$filename")
|
|
case "$extendedtype" in
|
|
*' ID3 '*)
|
|
type=MP3
|
|
;;
|
|
*'Musepack '*)
|
|
getRateChannelMPC
|
|
tryAPE
|
|
;;
|
|
*)
|
|
getRateChannelSoxi
|
|
tryAPE
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
if [ -n "$type" ]
|
|
then
|
|
getInfos::$type
|
|
else
|
|
tagreader=$getTags_version
|
|
fi
|
|
}
|
|
|
|
checkCopy() {
|
|
(
|
|
[ -z "${destinationfrequency[$destination]}" ] \
|
|
|| (( ${rate:-0} == ${destinationfrequency[$destination]} ))
|
|
) && (
|
|
[ -z "${destinationchannels[$destination]}" ] \
|
|
|| (( ${channels:-0} == ${destinationchannels[$destination]} ))
|
|
) && (
|
|
(( ${bitrate:-1000} == ${destinationquality[$destination]} )) \
|
|
|| (
|
|
[ -n "${destinationmaxbps[$destination]}" ] \
|
|
|| ((
|
|
${bitrate:-1000}
|
|
<= ${destinationmaxbps[$destination]:-0}
|
|
))
|
|
)
|
|
)
|
|
}
|
|
|
|
decodeSox() {
|
|
commandline=(sox --single-threaded --temp "$tempdir")
|
|
soxoptions_in=''
|
|
soxoptions_out=''
|
|
if (( ${destinationnormalize["$destination"]} ))
|
|
then
|
|
commandline+=(--norm)
|
|
soxoptions_in+=' --norm'
|
|
fi
|
|
if [ -n "$1" ]
|
|
then
|
|
commandline+=("$1")
|
|
else
|
|
commandline+=("$sourcepath/$filename")
|
|
fi
|
|
if [ -n "${destinationfrequency["$destination"]}" ] \
|
|
&& (( ${rate:-0} != ${destinationfrequency["$destination"]} ))
|
|
then
|
|
commandline+=(-r ${destinationfrequency["$destination"]})
|
|
soxoptions_out+=" -r ${destinationfrequency["$destination"]}"
|
|
fi
|
|
if [ -n "${destinationchannels["$destination"]}" ] \
|
|
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
|
then
|
|
commandline+=(-c ${destinationchannels["$destination"]})
|
|
soxoptions_out+=" -c ${destinationchannels["$destination"]}"
|
|
fi
|
|
tmpfile="$fileid${soxoptions_in// /}${soxoptions_out// /}"
|
|
commandline+=("$tempdir/$tmpfile.wav")
|
|
}
|
|
|
|
decodeMpcdec() {
|
|
tmpfile="${fileid}mpcdec"
|
|
commandline=(mpcdec "$sourcepath/$filename" "$tempdir/$tmpfile.wav")
|
|
}
|
|
|
|
decodeFile() {
|
|
if ! decodetaskid=$(
|
|
Select tasks id <<<"key = $tmpfile"
|
|
)
|
|
then
|
|
decodetaskid=$(
|
|
Insert tasks <<-EOInsert
|
|
key $tmpfile
|
|
source_file $fileid
|
|
$(
|
|
for key in ${!commandline[@]}
|
|
do
|
|
echo "cmd_arg$key ${commandline[key]}"
|
|
done
|
|
)
|
|
status 0
|
|
EOInsert
|
|
)
|
|
progressSpin
|
|
fi
|
|
if (( sox_needed ))
|
|
then
|
|
cleanup="$tempdir/$tmpfile"
|
|
decodeSox "$tempdir/$tmpfile.wav"
|
|
if ! soxtaskid=$(
|
|
Select tasks id <<<"key = $tmpfile"
|
|
)
|
|
then
|
|
soxtaskid=$(
|
|
Insert tasks <<-EOInsert
|
|
key $tmpfile
|
|
source_file $fileid
|
|
$(
|
|
for key in ${!commandline[@]}
|
|
do
|
|
echo "cmd_arg$key ${commandline[key]}"
|
|
done
|
|
)
|
|
requires $decodetaskid
|
|
required $decodetaskid
|
|
status 0
|
|
cleanup $cleanup
|
|
EOInsert
|
|
)
|
|
progressSpin
|
|
fi
|
|
fi
|
|
}
|
|
|
|
copyFile() {
|
|
extension="${filename##*.}"
|
|
cp -al \
|
|
"$sourcepath/$filename" \
|
|
"$destdir/$destfile.$extension" \
|
|
2>/dev/null \
|
|
|| cp -a \
|
|
"$sourcepath/$filename" \
|
|
"$destdir/$destfile.$extension"
|
|
echo \
|
|
"UPDATE destination_files" \
|
|
"SET filename=\"${filename//\"/\"\"}\"," \
|
|
" last_change=(" \
|
|
" SELECT last_change" \
|
|
" FROM source_files" \
|
|
" WHERE id=$fileid" \
|
|
" )," \
|
|
" old_filename=(" \
|
|
" SELECT filename" \
|
|
" FROM destination_files" \
|
|
" WHERE id=$destfileid" \
|
|
" )," \
|
|
" rename_pattern=" \
|
|
"\"${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}\""\
|
|
"WHERE id=$destfileid;" \
|
|
>&3
|
|
(( ++copies ))
|
|
}
|
|
|
|
sanitizeFile() {
|
|
shopt -s extglob
|
|
string="$1"
|
|
# Filenames can't contain /
|
|
string="${string//\// }"
|
|
if (( ${destinationfat32compat[$destination]} ))
|
|
then
|
|
# Filenames can't contain:
|
|
string=${string//\?/ }
|
|
string=${string//\\/ }
|
|
string=${string//</ }
|
|
string=${string//>/ }
|
|
string=${string//:/ }
|
|
string=${string//\*/ }
|
|
string=${string//|/ }
|
|
string=${string//\"/ }
|
|
|
|
# Filenames can't begin or end with ' '
|
|
string=${string/#+( )/}
|
|
string=${string/%+( )/}
|
|
fi
|
|
echo "$string"
|
|
}
|
|
|
|
getDestDir() {
|
|
destdir="${destinationpath[$destination]}/"
|
|
if [ -n "${destinationrenamepath[$destination]}" ]
|
|
then
|
|
destdir+="${destinationrenamepath[$destination]//%\{album\}/$album}"
|
|
replace=$(sanitizeFile "$albumartist")
|
|
destdir="${destdir//%\{albumartist\}/$replace}"
|
|
replace=$(sanitizeFile "$artist")
|
|
destdir="${destdir//%\{artist\}/$replace}"
|
|
replace=$(sanitizeFile "$genre")
|
|
destdir="${destdir//%\{genre\}/$replace}"
|
|
replace=$(sanitizeFile "$title")
|
|
destdir="${destdir//%\{title\}/$replace}"
|
|
tracknumber="${track%/*}"
|
|
replace=$(sanitizeFile "$tracknumber")
|
|
destdir="${destdir//%\{track\}/$replace}"
|
|
replace=$(sanitizeFile "$year")
|
|
destdir="${destdir//%\{year\}/$replace}"
|
|
replace=$(sanitizeFile "$disc")
|
|
destdir="${destdir//%\{disc\}/$replace}"
|
|
else
|
|
destdir+=$(sanitizeFile "${filename%%/*}")
|
|
part=${filename#*/}
|
|
while [[ $part =~ / ]]
|
|
do
|
|
destdir+="/$(sanitizeFile "${part%%/*}")"
|
|
part=${part#*/}
|
|
done
|
|
fi
|
|
if ! [ -d "$destdir" ]
|
|
then
|
|
mkdir -p "$destdir"
|
|
fi
|
|
}
|
|
|
|
getDestFile() {
|
|
if [ -n "${destinationrename[$destination]}" ]
|
|
then
|
|
destfile="${destinationrename[$destination]//%\{album\}/$album}"
|
|
destfile="${destfile//%\{albumartist\}/$albumartist}"
|
|
destfile="${destfile//%\{artist\}/$artist}"
|
|
destfile="${destfile//%\{genre\}/$genre}"
|
|
destfile="${destfile//%\{title\}/$title}"
|
|
tracknumber="${track%/*}"
|
|
destfile="${destfile//%\{track\}/$tracknumber}"
|
|
destfile="${destfile//%\{year\}/$year}"
|
|
destfile="${destfile//%\{disc\}/$disc}"
|
|
else
|
|
destfile="${filename##*/}"
|
|
destfile="${destfile%.*}"
|
|
fi
|
|
destfile=$(sanitizeFile "$destfile")
|
|
}
|
|
|
|
encodeFile::mp3() {
|
|
lameopts=(lame --quiet -v --abr ${destinationquality[$destination]})
|
|
[ -n "$album" ] && lameopts+=(--tl "$album" )
|
|
[ -n "$artist" ] && lameopts+=(--ta "$artist")
|
|
[ -n "$genre" ] && lameopts+=(--tg "$genre")
|
|
[ -n "$title" ] && lameopts+=(--tt "$title")
|
|
[ -n "$track" ] && lameopts+=(--tn "$track")
|
|
[ -n "$year" ] && lameopts+=(--ty "$year")
|
|
if (( ${destinationnoresample[$destination]:-0} == 1 ))
|
|
then
|
|
# If 'rate' is not one of these value, it cannot be encoded to
|
|
# MP3, in which case we'd be better of letting lame decide which
|
|
# rate to use.
|
|
if [ -n "${destinationfrequency["$destination"]}" ]
|
|
then
|
|
case ${destinationfrequency["$destination"]} in
|
|
48000) lameopts+=(--resample 48) ;;
|
|
44100) lameopts+=(--resample 44.1) ;;
|
|
32000) lameopts+=(--resample 32) ;;
|
|
24000) lameopts+=(--resample 24) ;;
|
|
22050) lameopts+=(--resample 22.05) ;;
|
|
16000) lameopts+=(--resample 16) ;;
|
|
12000) lameopts+=(--resample 12) ;;
|
|
11025) lameopts+=(--resample 11.025) ;;
|
|
8000) lameopts+=(--resample 8) ;;
|
|
esac
|
|
else
|
|
case $rate in
|
|
48000) lameopts+=(--resample 48) ;;
|
|
44100) lameopts+=(--resample 44.1) ;;
|
|
32000) lameopts+=(--resample 32) ;;
|
|
24000) lameopts+=(--resample 24) ;;
|
|
22050) lameopts+=(--resample 22.05) ;;
|
|
16000) lameopts+=(--resample 16) ;;
|
|
12000) lameopts+=(--resample 12) ;;
|
|
11025) lameopts+=(--resample 11.025) ;;
|
|
8000) lameopts+=(--resample 8) ;;
|
|
esac
|
|
fi
|
|
fi
|
|
lameopts+=("$tempdir/$tmpfile.wav" "$destdir/$destfile.mp3")
|
|
encodetaskid=$(
|
|
Insert tasks <<-EOInsert
|
|
key ${fileid}lame$destination
|
|
requires ${soxtaskid:-$decodetaskid}
|
|
required ${soxtaskid:-$decodetaskid}
|
|
fileid $destfileid
|
|
filename $destdir/$destfile.mp3
|
|
$(
|
|
for key in ${!lameopts[@]}
|
|
do
|
|
echo "cmd_arg$key ${lameopts[key]}"
|
|
done
|
|
)
|
|
cleanup $tempdir/$tmpfile.wav
|
|
source_file $fileid
|
|
status 0
|
|
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}
|
|
EOInsert
|
|
)
|
|
progressSpin
|
|
}
|
|
|
|
encodeFile::opus() {
|
|
opusencopts=(opusenc --music --quiet)
|
|
opusencopts+=(--bitrate ${destinationquality[$destination]})
|
|
[ -n "${destinationloss["$destination"]}" ] \
|
|
&& opusencopts+=(--expect-loss "${destinationloss["$destination"]}")
|
|
[ -n "$albumartist" ] && opusencopts+=(--comment "ALBUMARTIST=$albumartist")
|
|
[ -n "$album" ] && opusencopts+=(--comment "ALBUM=$album")
|
|
[ -n "$artist" ] && opusencopts+=(--artist "$artist")
|
|
[ -n "$composer" ] && opusencopts+=(--comment "COMPOSER=$composer")
|
|
[ -n "$disc" ] && opusencopts+=(--comment "DISCNUMBER=$disc")
|
|
[ -n "$genre" ] && opusencopts+=(--comment "GENRE=$genre")
|
|
[ -n "$performer" ] && opusencopts+=(--comment "PERFORMER=$performer")
|
|
[ -n "$title" ] && opusencopts+=(--title "$title")
|
|
[ -n "$track" ] && opusencopts+=(--comment "TRACKNUMBER=${track%/*}")
|
|
[ -n "${track#*/}" ] && opusencopts+=(--comment "TRACKTOTAL=${track#*/}")
|
|
[ -n "$year" ] && opusencopts+=(--comment "DATE=$year")
|
|
opusencopts+=("$tempdir/$tmpfile.wav" "$destdir/$destfile.opus")
|
|
encodetaskid=$(
|
|
Insert tasks <<-EOInsert
|
|
key ${fileid}opusenc$destination
|
|
requires ${soxtaskid:-$decodetaskid}
|
|
required ${soxtaskid:-$decodetaskid}
|
|
fileid $destfileid
|
|
filename $destdir/$destfile.ogg
|
|
$(
|
|
for key in ${!opusencopts[@]}
|
|
do
|
|
echo "cmd_arg$key ${opusencopts[key]}"
|
|
done
|
|
)
|
|
cleanup $tempdir/$tmpfile.wav
|
|
source_file $fileid
|
|
status 0
|
|
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}
|
|
EOInsert
|
|
)
|
|
progressSpin
|
|
}
|
|
|
|
encodeFile::vorbis() {
|
|
oggencopts=(oggenc -Q -q ${destinationquality[$destination]})
|
|
[ -n "$albumartist" ] && oggencopts+=(-c "ALBUMARTIST=$albumartist")
|
|
[ -n "$album" ] && oggencopts+=(-l "$album")
|
|
[ -n "$artist" ] && oggencopts+=(-a "$artist")
|
|
[ -n "$composer" ] && oggencopts+=(-c "COMPOSER=$composer")
|
|
[ -n "$disc" ] && oggencopts+=(-c "DISCNUMBER=$disc")
|
|
[ -n "$genre" ] && oggencopts+=(-G "$genre")
|
|
[ -n "$performer" ] && oggencopts+=(-c "PERFORMER=$performer")
|
|
[ -n "$title" ] && oggencopts+=(-t "$title")
|
|
[ -n "$track" ] && oggencopts+=(-N "$track")
|
|
[ -n "$year" ] && oggencopts+=(-d "$year")
|
|
oggencopts+=(-o "$destdir/$destfile.ogg" "$tempdir/$tmpfile.wav")
|
|
encodetaskid=$(
|
|
Insert tasks <<-EOInsert
|
|
key ${fileid}oggenc$destination
|
|
requires ${soxtaskid:-$decodetaskid}
|
|
required ${soxtaskid:-$decodetaskid}
|
|
fileid $destfileid
|
|
filename $destdir/$destfile.ogg
|
|
$(
|
|
for key in ${!oggencopts[@]}
|
|
do
|
|
echo "cmd_arg$key ${oggencopts[key]}"
|
|
done
|
|
)
|
|
cleanup $tempdir/$tmpfile.wav
|
|
source_file $fileid
|
|
status 0
|
|
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}
|
|
EOInsert
|
|
)
|
|
progressSpin
|
|
}
|
|
|
|
worker() {
|
|
exec 2>>"$tempdir/worker$1.log"
|
|
(( debug >= 2 )) && echo "${cmd_arg[@]}" >&2
|
|
"${cmd_arg[@]}" >/dev/null
|
|
}
|
|
|
|
master() {
|
|
if (( active >= concurrency)) || [ -n "$quit" ]
|
|
then
|
|
sleep 0.1
|
|
else
|
|
echo '
|
|
SELECT COUNT(*)
|
|
FROM tasks
|
|
WHERE status = 0;
|
|
|
|
SELECT COUNT(*)
|
|
FROM tasks
|
|
WHERE status = 0
|
|
AND requires is NULL;
|
|
|
|
SELECT
|
|
id,
|
|
source_file,
|
|
required,
|
|
cmd_arg0,
|
|
cmd_arg1,
|
|
cmd_arg2,
|
|
cmd_arg3,
|
|
cmd_arg4,
|
|
cmd_arg5,
|
|
cmd_arg6,
|
|
cmd_arg7,
|
|
cmd_arg8,
|
|
cmd_arg9,
|
|
cmd_arg10,
|
|
cmd_arg11,
|
|
cmd_arg12,
|
|
cmd_arg13,
|
|
cmd_arg14,
|
|
cmd_arg15,
|
|
cmd_arg16,
|
|
cmd_arg17,
|
|
cmd_arg18,
|
|
cmd_arg19,
|
|
cmd_arg20,
|
|
cmd_arg21,
|
|
cmd_arg22,
|
|
cmd_arg23,
|
|
cmd_arg24,
|
|
cmd_arg25,
|
|
cmd_arg26,
|
|
cmd_arg27,
|
|
cmd_arg28,
|
|
cmd_arg29,
|
|
cleanup,
|
|
fileid,
|
|
filename
|
|
FROM tasks
|
|
WHERE status = 0
|
|
AND requires is NULL
|
|
ORDER BY source_file
|
|
LIMIT 1;
|
|
' >&3
|
|
read -u4 remaining
|
|
read -u4 ready
|
|
if (( remaining == 0 ))
|
|
then
|
|
sleep 0.1
|
|
continue
|
|
elif (( ready == 0 ))
|
|
then
|
|
sleep 0.1
|
|
else
|
|
(( ++active ))
|
|
read -u4 line
|
|
taskid=${line%%|*}
|
|
rest="${line#*|}|"
|
|
sourcefileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
required=${rest%%|*}
|
|
rest=${rest#*|}
|
|
cmd_arg=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cmd_arg+=("${rest%%|*}")
|
|
rest=${rest#*|}
|
|
cleanup=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destfileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destfilename=${rest%%|*}
|
|
rest=${rest#*|}
|
|
for key in ${!cmd_arg[@]}
|
|
do
|
|
[ -z "${cmd_arg[key]}" ] && unset cmd_arg[key]
|
|
done
|
|
workerid=$(getworkerid)
|
|
workertasks[workerid]=$taskid
|
|
Update tasks status 1 <<<"id = $taskid"
|
|
createworker $workerid
|
|
fi
|
|
fi
|
|
}
|
|
|
|
getworkerid() {
|
|
local i
|
|
for (( i=0 ; i >= 0 ; i++ ))
|
|
do
|
|
if [ -z "${workers[i]}" ]
|
|
then
|
|
echo $i
|
|
return
|
|
fi
|
|
done
|
|
# If we reach this, we have reached the signed long limit
|
|
# (2^63 - 1 = 9223372036854775807 - Got a supercomputer?)
|
|
(( concurrency-- ))
|
|
}
|
|
|
|
createworker() {
|
|
worker $1 &
|
|
workers[$1]=$!
|
|
}
|
|
|
|
destroyworker() {
|
|
dyingworker=${workers[$1]}
|
|
unset workers[$1]
|
|
wait $dyingworker
|
|
}
|
|
|
|
gettaskinfos() {
|
|
echo '
|
|
SELECT
|
|
id,
|
|
source_file,
|
|
required,
|
|
cleanup,
|
|
fileid,
|
|
filename
|
|
FROM tasks
|
|
WHERE id='$1';
|
|
' >&3
|
|
read -u4 line
|
|
taskid=${line%%|*}
|
|
rest="${line#*|}|"
|
|
sourcefileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
required=${rest%%|*}
|
|
rest=${rest#*|}
|
|
cleanup=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destfileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destfilename=${rest%%|*}
|
|
rest=${rest#*|}
|
|
}
|
|
|
|
cleaner() {
|
|
for key in ${!failedtasks[@]}
|
|
do
|
|
taskid=${failedtasks[key]}
|
|
gettaskinfos $taskid
|
|
faildepends=$(
|
|
Select tasks 'COUNT(*)' <<-EOWhere
|
|
requires = $taskid
|
|
EOWhere
|
|
)
|
|
(( failed+=faildepends ))
|
|
Update tasks status 2 <<<"id = $taskid"
|
|
Update tasks status 2 <<<"requires = $taskid"
|
|
echo "SELECT COUNT(*)
|
|
FROM tasks
|
|
WHERE ( status = 0 OR status = 1 )
|
|
AND required = $taskid;">&3
|
|
read -u4 count
|
|
if (( count == 0 ))
|
|
then
|
|
rm -f "$cleanup"
|
|
fi
|
|
unset failedtasks[key]
|
|
done
|
|
for key in ${!finishedtasks[@]}
|
|
do
|
|
taskid=${finishedtasks[key]}
|
|
gettaskinfos $taskid
|
|
if [ -n "$destfilename" ]
|
|
then
|
|
echo \
|
|
"UPDATE destination_files" \
|
|
"SET filename=\"${destfilename//\"/\"\"}\"," \
|
|
" last_change=(" \
|
|
" SELECT last_change" \
|
|
" FROM source_files" \
|
|
" WHERE id=$sourcefileid" \
|
|
" )," \
|
|
" old_filename=(" \
|
|
" SELECT filename" \
|
|
" FROM destination_files" \
|
|
" WHERE id=$destfileid" \
|
|
" )," \
|
|
" rename_pattern=(" \
|
|
" SELECT rename_pattern" \
|
|
" FROM tasks" \
|
|
" WHERE id=$taskid" \
|
|
" )" \
|
|
"WHERE id=$destfileid;" \
|
|
>&3
|
|
fi
|
|
echo "SELECT COUNT(*)
|
|
FROM tasks
|
|
WHERE ( status = 0 OR status = 1 )
|
|
AND required = $taskid;">&3
|
|
read -u4 count
|
|
if (( count == 0 ))
|
|
then
|
|
rm -f "$cleanup"
|
|
fi
|
|
Delete tasks <<<"id = $taskid"
|
|
unset finishedtasks[key]
|
|
done
|
|
}
|
|
|
|
checkworkers() {
|
|
for key in ${!workers[@]}
|
|
do
|
|
if ! kill -0 ${workers[key]} 2>/dev/null
|
|
then
|
|
taskid=${workertasks[key]}
|
|
(( ++ran ))
|
|
(( active-- ))
|
|
if destroyworker $key
|
|
then
|
|
finishedtasks+=($taskid)
|
|
else
|
|
failedtasks+=($taskid)
|
|
(( ++failed ))
|
|
fi
|
|
unset workertasks[key]
|
|
fi
|
|
done
|
|
}
|
|
|
|
#UI
|
|
|
|
if [ ! -f ~/.atom/atom.cfg ]
|
|
then
|
|
if [ ! -d ~/.atom ]
|
|
then
|
|
mkdir -p ~/.atom
|
|
fi
|
|
sed "s:%HOME%:$HOME:" "$exampleconf" > ~/.atom/atom.cfg
|
|
cat >&2 <<-EOCfgNotice
|
|
No configuration file found!
|
|
An example file has been created as ~/.atom/atom.cfg.
|
|
You should change it to your likings using you favorite editor.
|
|
|
|
Bailing out.
|
|
EOCfgNotice
|
|
exit $ENOCFG
|
|
fi
|
|
getConfig
|
|
|
|
set +H
|
|
if (( debug ))
|
|
then
|
|
cat <<-EOF
|
|
General|Load|$maxload
|
|
|Load Interval|$loadinterval
|
|
|Temp Dir|$tempdir
|
|
|Database|$database
|
|
|Debug|$debug
|
|
Source|Path|$sourcepath
|
|
EOF
|
|
for prune_expression in "${skippeddirectories[@]}"
|
|
do
|
|
echo "|Skipped directory|$prune_expression"
|
|
done
|
|
for destination in ${!destinationpath[@]}
|
|
do
|
|
cat <<-EOF
|
|
$destination|Path|${destinationpath[$destination]}
|
|
|Format|${destinationformat[$destination]}
|
|
|Quality|${destinationquality[$destination]}
|
|
|Normalize|${destinationnormalize[$destination]}
|
|
|Channels|${destinationchannels[$destination]}
|
|
|Frequency|${destinationfrequency[$destination]}
|
|
|Path Change|${destinationrenamepath[$destination]}
|
|
|File Rename|${destinationrename[$destination]}
|
|
EOF
|
|
echo "|Skipped mime-type|${destinationskipmime[$destination]//\|/
|
|
|Skipped mime-type|}"
|
|
echo "|Copied mime-type|${destinationcopymime[$destination]//\|/
|
|
|Copied mime-type|}"
|
|
done
|
|
fi |column -t -s'|' -n
|
|
|
|
openDatabase
|
|
|
|
createDestinations
|
|
|
|
getFiles
|
|
|
|
updateMimes
|
|
|
|
removeObsoleteFiles
|
|
|
|
echo '
|
|
SELECT id,
|
|
filename
|
|
FROM destination_files
|
|
WHERE source_file_id is NULL;
|
|
|
|
SELECT "AtOM:NoMoreFiles";
|
|
' >&3
|
|
|
|
deleted=0
|
|
removed=0
|
|
read -u4 line
|
|
until [[ $line == AtOM:NoMoreFiles ]]
|
|
do
|
|
id=${line%|*}
|
|
filename=${line#*|}
|
|
if [ -n "$filename" ]
|
|
then
|
|
if rm -f "$filename"
|
|
then
|
|
Delete destination_files <<<"id = $id"
|
|
(( ++deleted ))
|
|
fi
|
|
else
|
|
Delete destination_files <<<"id = $id"
|
|
(( ++removed ))
|
|
fi
|
|
read -u4 line
|
|
done
|
|
echo "Suppressed $deleted files, $removed removed from database"
|
|
|
|
# get files
|
|
for reader in "${tagreaders[@]}"
|
|
do
|
|
tagreaderclause+="${tagreaderclause:+ AND }NOT tags.tagreader = \"$reader\""
|
|
done
|
|
echo '
|
|
SELECT COUNT(DISTINCT source_files.filename)
|
|
FROM source_files
|
|
INNER JOIN destination_files
|
|
ON destination_files.source_file_id=source_files.id
|
|
INNER JOIN destinations
|
|
ON destination_files.destination_id=destinations.id
|
|
INNER JOIN mime_type_actions
|
|
ON destinations.id=mime_type_actions.destination_id
|
|
INNER JOIN tags
|
|
ON source_files.id=tags.source_file
|
|
WHERE mime_type_actions.id = source_files.mime_type
|
|
AND (
|
|
CAST(tags.last_change AS TEXT)
|
|
<>
|
|
CAST(source_files.last_change AS TEXT)
|
|
OR ('"$tagreaderclause"')
|
|
)
|
|
AND mime_type_actions.action = 1;' >&3
|
|
read -u4 filecount
|
|
echo '
|
|
SELECT DISTINCT
|
|
source_files.id,
|
|
source_files.last_change,
|
|
mime_type_actions.mime_text,
|
|
source_files.filename
|
|
FROM source_files
|
|
INNER JOIN destination_files
|
|
ON destination_files.source_file_id=source_files.id
|
|
INNER JOIN destinations
|
|
ON destination_files.destination_id=destinations.id
|
|
INNER JOIN mime_type_actions
|
|
ON destinations.id=mime_type_actions.destination_id
|
|
INNER JOIN tags
|
|
ON source_files.id=tags.source_file
|
|
WHERE mime_type_actions.id = source_files.mime_type
|
|
AND (
|
|
CAST(tags.last_change AS TEXT)
|
|
<>
|
|
CAST(source_files.last_change AS TEXT)
|
|
OR ('"$tagreaderclause"')
|
|
)
|
|
AND mime_type_actions.action = 1;
|
|
|
|
SELECT "AtOM:NoMoreFiles";' >&3
|
|
read -u4 line
|
|
while ! [[ $line = AtOM:NoMoreFiles ]]
|
|
do
|
|
tagfiles+=("$line")
|
|
read -u4 line
|
|
done
|
|
echo 'BEGIN TRANSACTION;' >&3
|
|
for line in "${tagfiles[@]}"
|
|
do
|
|
sourcefileid=${line%%|*}
|
|
rest=${line#*|}
|
|
lastchange=${rest%%|*}
|
|
rest=${rest#*|}
|
|
mimetype=${rest%%|*}
|
|
filename=${rest#*|}
|
|
echo -en "\rTags: $((++count*100/filecount))%"
|
|
if (( count % 1000 == 0 ))
|
|
then
|
|
echo 'COMMIT;BEGIN TRANSACTION;' >&3
|
|
(( debug )) \
|
|
&& echo -n " $count files read, committing..."
|
|
fi
|
|
if getTags
|
|
then
|
|
Update tags \
|
|
album "${album:-NULL}" \
|
|
albumartist "${albumartist:-NULL}" \
|
|
artist "${artist:-NULL}" \
|
|
composer "${composer:-NULL}" \
|
|
disc "${disc:-NULL}" \
|
|
genre "${genre:-NULL}" \
|
|
performer "${performer:-NULL}" \
|
|
title "${title:-NULL}" \
|
|
track "${tracknum:-NULL}" \
|
|
year "${year:-NULL}" \
|
|
last_change "$lastchange" \
|
|
rate "${rate:-NULL}" \
|
|
channels "${channels:-NULL}" \
|
|
bitrate "${bitrate:-NULL}" \
|
|
tagreader "$tagreader" \
|
|
>/dev/null <<<"source_file = $sourcefileid"
|
|
unset genre \
|
|
albumartist \
|
|
year \
|
|
album \
|
|
disc \
|
|
artist \
|
|
tracknum \
|
|
title \
|
|
composer \
|
|
performer \
|
|
rate \
|
|
bitrate \
|
|
channels
|
|
fi
|
|
done
|
|
echo 'COMMIT;' >&3
|
|
echo -e "\rRead tags from ${count:-0} files."
|
|
unset count tagfiles
|
|
|
|
echo '
|
|
CREATE TEMPORARY TABLE tasks(
|
|
id INTEGER PRIMARY KEY,
|
|
key TEXT UNIQUE,
|
|
rename_pattern TEXT,
|
|
source_file INTEGER,
|
|
fileid INTEGER,
|
|
filename TEXT,
|
|
cmd_arg0 TEXT,
|
|
cmd_arg1 TEXT,
|
|
cmd_arg2 TEXT,
|
|
cmd_arg3 TEXT,
|
|
cmd_arg4 TEXT,
|
|
cmd_arg5 TEXT,
|
|
cmd_arg6 TEXT,
|
|
cmd_arg7 TEXT,
|
|
cmd_arg8 TEXT,
|
|
cmd_arg9 TEXT,
|
|
cmd_arg10 TEXT,
|
|
cmd_arg11 TEXT,
|
|
cmd_arg12 TEXT,
|
|
cmd_arg13 TEXT,
|
|
cmd_arg14 TEXT,
|
|
cmd_arg15 TEXT,
|
|
cmd_arg16 TEXT,
|
|
cmd_arg17 TEXT,
|
|
cmd_arg18 TEXT,
|
|
cmd_arg19 TEXT,
|
|
cmd_arg20 TEXT,
|
|
cmd_arg21 TEXT,
|
|
cmd_arg22 TEXT,
|
|
cmd_arg23 TEXT,
|
|
cmd_arg24 TEXT,
|
|
cmd_arg25 TEXT,
|
|
cmd_arg26 TEXT,
|
|
cmd_arg27 TEXT,
|
|
cmd_arg28 TEXT,
|
|
cmd_arg29 TEXT,
|
|
requires INTEGER,
|
|
required INTEGER,
|
|
status INTEGER NOT NULL,
|
|
cleanup TEXT,
|
|
FOREIGN KEY(requires) REFERENCES tasks(id)
|
|
ON DELETE SET NULL
|
|
);
|
|
CREATE INDEX tasks_by_key ON tasks ( key );
|
|
CREATE INDEX tasks_by_sourcefile ON tasks ( source_file );
|
|
' >&3
|
|
|
|
echo '
|
|
SELECT COUNT(source_files.id)
|
|
FROM source_files
|
|
INNER JOIN destination_files
|
|
ON source_files.id
|
|
= destination_files.source_file_id
|
|
INNER JOIN destinations
|
|
ON destination_files.destination_id=destinations.id
|
|
INNER JOIN mime_type_actions
|
|
ON mime_type_actions.id = source_files.mime_type
|
|
INNER JOIN tags
|
|
ON source_files.id = tags.source_file
|
|
WHERE CAST(destination_files.last_change AS TEXT)
|
|
<> CAST(source_files.last_change AS TEXT)
|
|
AND mime_type_actions.destination_id = destinations.id
|
|
AND mime_type_actions.action = 1;' >&3
|
|
read -u4 filecount
|
|
echo '
|
|
SELECT
|
|
source_files.id,
|
|
source_files.filename,
|
|
mime_type_actions.mime_text,
|
|
destinations.name,
|
|
destination_files.id,
|
|
tags.rate,
|
|
tags.channels,
|
|
tags.bitrate,
|
|
tags.genre,
|
|
tags.albumartist,
|
|
tags.year,
|
|
tags.album,
|
|
tags.disc,
|
|
tags.artist,
|
|
tags.track,
|
|
tags.title,
|
|
tags.composer,
|
|
tags.performer
|
|
FROM source_files
|
|
INNER JOIN destination_files
|
|
ON source_files.id
|
|
= destination_files.source_file_id
|
|
INNER JOIN destinations
|
|
ON destination_files.destination_id=destinations.id
|
|
INNER JOIN mime_type_actions
|
|
ON mime_type_actions.id = source_files.mime_type
|
|
INNER JOIN tags
|
|
ON source_files.id = tags.source_file
|
|
WHERE CAST(destination_files.last_change AS TEXT)
|
|
<> CAST(source_files.last_change AS TEXT)
|
|
AND mime_type_actions.destination_id = destinations.id
|
|
AND mime_type_actions.action = 1;
|
|
|
|
SELECT "AtOM:NoMoreFiles";' >&3
|
|
read -u4 line
|
|
while ! [[ $line = AtOM:NoMoreFiles ]]
|
|
do
|
|
decodefiles+=("$line|")
|
|
read -u4 line
|
|
done
|
|
echo -n 'Creating tasks... '
|
|
|
|
echo 'BEGIN TRANSACTION;' >&3
|
|
for line in "${decodefiles[@]}"
|
|
do
|
|
fileid=${line%%|*}
|
|
rest=${line#*|}
|
|
filename=${rest%%|*}
|
|
rest=${rest#*|}
|
|
mimetype=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destination=${rest%%|*}
|
|
rest=${rest#*|}
|
|
destfileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
rate=${rest%%|*}
|
|
rest=${rest#*|}
|
|
channels=${rest%%|*}
|
|
rest=${rest#*|}
|
|
bitrate=${rest%%|*}
|
|
rest=${rest#*|}
|
|
genre=${rest%%|*}
|
|
rest=${rest#*|}
|
|
albumartist=${rest%%|*}
|
|
rest=${rest#*|}
|
|
year=${rest%%|*}
|
|
rest=${rest#*|}
|
|
album=${rest%%|*}
|
|
rest=${rest#*|}
|
|
disc=${rest%%|*}
|
|
rest=${rest#*|}
|
|
artist=${rest%%|*}
|
|
rest=${rest#*|}
|
|
track=${rest%%|*}
|
|
rest=${rest#*|}
|
|
title=${rest%%|*}
|
|
rest=${rest#*|}
|
|
composer=${rest%%|*}
|
|
rest=${rest#*|}
|
|
performer=${rest%%|*}
|
|
unset rest
|
|
case "$mimetype" in
|
|
'audio/mpeg')
|
|
if [[ ${destinationformat[$destination]} = mp3 ]] \
|
|
&& checkCopy
|
|
then
|
|
copied=1
|
|
else
|
|
decodeSox
|
|
fi
|
|
;;
|
|
'application/ogg')
|
|
if [[ ${destinationformat[$destination]} = vorbis ]] \
|
|
&& checkCopy
|
|
then
|
|
copied=1
|
|
else
|
|
decodeSox
|
|
fi
|
|
;;
|
|
'audio/x-flac')
|
|
decodeSox
|
|
;;
|
|
*)
|
|
extendedtype=$(file -b "$sourcepath/$filename")
|
|
case "$extendedtype" in
|
|
*'Musepack '*)
|
|
decodeMpcdec
|
|
if (( ${destinationnormalize["$destination"]}))\
|
|
|| (
|
|
[ -n "${destinationfrequency["$destination"]}" ]\
|
|
&& (( ${rate:-0} != ${destinationfrequency["$destination"]}))\
|
|
) || (
|
|
[ -n "${destinationchannels["$destination"]}" ]\
|
|
&& (( ${channels:-0} != ${destinationchannels["$destination"]} ))
|
|
)
|
|
then
|
|
sox_needed=1
|
|
fi
|
|
;;
|
|
*)
|
|
decodeSox
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
if ! (( copied ))
|
|
then
|
|
decodeFile
|
|
fi
|
|
getDestDir
|
|
getDestFile
|
|
if (( copied ))
|
|
then
|
|
copyFile
|
|
else
|
|
encodeFile::${destinationformat[$destination]}
|
|
fi
|
|
unset \
|
|
album \
|
|
albumartist \
|
|
artist \
|
|
bitrate \
|
|
channels \
|
|
commandline \
|
|
composer \
|
|
copied \
|
|
decodetaskid \
|
|
destfileid \
|
|
destination \
|
|
disc \
|
|
fileid \
|
|
filename \
|
|
mimetype \
|
|
performer \
|
|
rate \
|
|
rest \
|
|
sox_needed \
|
|
soxoptions_in \
|
|
soxoptions_out \
|
|
soxtaskid \
|
|
title \
|
|
track \
|
|
year \
|
|
tmpfile
|
|
done
|
|
echo 'COMMIT;' >&3
|
|
echo -e "\rCreated ${count:-0} tasks for $filecount files (${copies:-0} immediate copies)"
|
|
|
|
concurrency=$(( maxload / 2 ))
|
|
(( concurrency )) || concurrency=1
|
|
active=0
|
|
#set -x
|
|
for (( i=0 ; i < concurrency ; i++ ))
|
|
do
|
|
master
|
|
done
|
|
concurrencychange=$(date +%s)
|
|
starttime=$concurrencychange
|
|
taskcount=$count
|
|
failed=0
|
|
while (( ${#workers[@]} ))
|
|
do
|
|
if read -n 1 -t 0.1 userinput
|
|
then
|
|
case $userinput in
|
|
'+')
|
|
((maxload++))
|
|
;;
|
|
'-')
|
|
((--maxload)) || ((maxload=1))
|
|
;;
|
|
[qQ])
|
|
quit=1
|
|
;;
|
|
esac
|
|
fi
|
|
read humanload garbage < /proc/loadavg
|
|
load=${humanload%.*}
|
|
if [ -z "$quit" ] && (( $(date +%s)-concurrencychange >= loadinterval ))
|
|
then
|
|
if (( concurrency > 1 )) \
|
|
&& (( load > maxload ))
|
|
then
|
|
concurrencychange=$(date +%s)
|
|
(( --concurrency ))
|
|
elif (( load < maxload )) && (( active > concurrency - 1 ))
|
|
then
|
|
concurrencychange=$(date +%s)
|
|
(( ++concurrency ))
|
|
fi
|
|
fi
|
|
checkworkers
|
|
cleaner
|
|
master
|
|
if (( ran ))
|
|
then
|
|
currenttime=$(date +%s)
|
|
avgduration=$((
|
|
((currenttime - starttime) * 1000)
|
|
/
|
|
ran
|
|
))
|
|
secsremaining=$(( remaining * avgduration / 1000 ))
|
|
(( days =
|
|
secsremaining
|
|
/
|
|
( 24*60*60 )
|
|
)) || true
|
|
(( hours =
|
|
( secsremaining - ( days*24*60*60 ) )
|
|
/
|
|
( 60*60 )
|
|
)) || true
|
|
(( minutes =
|
|
( secsremaining - ( ( days*24 + hours ) *60*60 ) )
|
|
/
|
|
60
|
|
)) || true
|
|
(( seconds =
|
|
secsremaining
|
|
-
|
|
( ( ( ( days*24 + hours ) *60 ) + minutes ) *60 )
|
|
)) || true
|
|
avgduration=$(printf %04i $avgduration)
|
|
avgdsec=${avgduration:0:-3}
|
|
avgdmsec=${avgduration#$avgdsec}
|
|
fi
|
|
dran=$(printf %${#taskcount}i ${ran:-0})
|
|
rtime=$(
|
|
printf '%2id %2ih%02im%02is' \
|
|
${days:-0} \
|
|
${hours:-0} \
|
|
${minutes:-0} \
|
|
${seconds:-0}
|
|
)
|
|
percent=$(printf %3i $((ran * 100 / taskcount)))
|
|
echo -en "\rL: $humanload/$maxload" \
|
|
"W: $active/$concurrency" \
|
|
"T: ${dran}/$taskcount (F:$failed) $percent% $rtime" \
|
|
"(A: ${avgdsec:--}.${avgdmsec:--}s/task)"
|
|
done
|
|
unset count
|
|
|
|
endtime=$(date +%s)
|
|
|
|
(( elapsedseconds = endtime - starttime ))
|
|
(( days =
|
|
elapsedseconds
|
|
/
|
|
( 24*60*60 )
|
|
)) || true
|
|
(( hours =
|
|
( elapsedseconds - ( days*24*60*60 ) )
|
|
/
|
|
( 60*60 )
|
|
)) || true
|
|
(( minutes =
|
|
( elapsedseconds - ( ( days*24 + hours ) *60*60 ) )
|
|
/
|
|
60
|
|
)) || true
|
|
(( seconds =
|
|
elapsedseconds
|
|
-
|
|
( ( ( ( days*24 + hours ) *60 ) + minutes ) *60 )
|
|
)) || true
|
|
|
|
echo -e "\rRan ${ran:=0} tasks, $failed of which failed, in $days" \
|
|
"days, $hours hours, $minutes minutes and $seconds seconds."
|
|
|
|
if [ -n "$quit" ]
|
|
then
|
|
closeDatabase
|
|
exit
|
|
fi
|
|
|
|
#set -x
|
|
for destination in "${!destinationpath[@]}"
|
|
do
|
|
echo '
|
|
SELECT
|
|
destination_files.filename,
|
|
destination_files.id,
|
|
source_files.filename,
|
|
tags.album,
|
|
tags.albumartist,
|
|
tags.artist,
|
|
tags.composer,
|
|
tags.disc,
|
|
tags.genre,
|
|
tags.performer,
|
|
tags.title,
|
|
tags.track,
|
|
tags.year
|
|
FROM destination_files
|
|
INNER JOIN destinations
|
|
ON destination_files.destination_id
|
|
=destinations.id
|
|
INNER JOIN tags
|
|
ON destination_files.source_file_id
|
|
=tags.source_file
|
|
INNER JOIN source_files
|
|
ON destination_files.source_file_id
|
|
=source_files.id
|
|
WHERE destinations.name="'"$destination"'"
|
|
AND (destination_files.rename_pattern
|
|
!=
|
|
"'"${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}"'"
|
|
OR destination_files.rename_pattern is NULL)
|
|
AND destination_files.last_change > 0
|
|
;
|
|
|
|
SELECT "AtOM:NoMoreFiles";
|
|
' >&3
|
|
|
|
read -u4 line
|
|
if [[ $line != AtOM:NoMoreFiles ]]
|
|
then
|
|
case "${destinationformat[$destination]}" in
|
|
'mp3') extension=mp3 ;;
|
|
'opus') extension=opus ;;
|
|
'vorbis') extension=ogg ;;
|
|
esac
|
|
echo -n "$destination: rename pattern changed, renaming files... "
|
|
while [[ $line != AtOM:NoMoreFiles ]]
|
|
do
|
|
oldfilename=${line%%|*}
|
|
rest=${line#*|}'|'
|
|
destfileid=${rest%%|*}
|
|
rest=${rest#*|}
|
|
filename=${rest%%|*}
|
|
rest=${rest#*|}
|
|
album=${rest%%|*}
|
|
rest=${rest#*|}
|
|
albumartist=${rest%%|*}
|
|
rest=${rest#*|}
|
|
artist=${rest%%|*}
|
|
rest=${rest#*|}
|
|
composer=${rest%%|*}
|
|
rest=${rest#*|}
|
|
disc=${rest%%|*}
|
|
rest=${rest#*|}
|
|
genre=${rest%%|*}
|
|
rest=${rest#*|}
|
|
performer=${rest%%|*}
|
|
rest=${rest#*|}
|
|
title=${rest%%|*}
|
|
rest=${rest#*|}
|
|
track=${rest%%|*}
|
|
rest=${rest#*|}
|
|
year=${rest%%|*}
|
|
rest=${rest#*|}
|
|
if [ -n "$oldfilename" -a -f "$oldfilename" ]
|
|
then
|
|
getDestDir
|
|
getDestFile
|
|
destfilename="$destdir/$destfile.$extension"
|
|
if [[ $oldfilename != $destfilename ]]
|
|
then
|
|
mv "$oldfilename" "$destfilename"
|
|
progressSpin
|
|
fi
|
|
echo "UPDATE destination_files" \
|
|
"SET filename=\"${destfilename//\"/\"\"}\"," \
|
|
" rename_pattern=" \
|
|
"\"${destinationrenamepath[$destination]}/${destinationrename[$destination]}:${destinationfat32compat["$destination"]}\"" \
|
|
"WHERE id=$destfileid;" \
|
|
>&3
|
|
fi
|
|
read -u4 line
|
|
done
|
|
echo $'\r'"$destination: Renamed ${count:-0} files "
|
|
fi
|
|
unset count
|
|
done
|
|
|
|
echo '
|
|
SELECT id,
|
|
old_filename
|
|
FROM destination_files
|
|
WHERE old_filename IS NOT NULL;
|
|
|
|
SELECT "AtOM:NoMoreFiles";
|
|
' >&3
|
|
|
|
echo "Removing obsolete files... "
|
|
read -u4 line
|
|
while [[ $line != AtOM:NoMoreFiles ]]
|
|
do
|
|
id=${line%%|*}
|
|
filename=${line#*|}
|
|
if [ -f "$filename" ]
|
|
then
|
|
rm -f "$filename"
|
|
fi
|
|
Update destination_files old_filename NULL <<<"id = $id"
|
|
progressSpin
|
|
read -u4 line
|
|
done
|
|
echo $'\r'"Removed ${count:-0} obsolete files."
|
|
|
|
echo "Purging empty directories."
|
|
for path in "${destinationpath[@]}"
|
|
do
|
|
find "$path" -type d -empty -delete
|
|
done
|
|
|
|
closeDatabase
|
|
|
|
# vim:set ts=8 sw=8:
|