Compare commits

...

56 Commits

Author SHA1 Message Date
ScriptFanix
7a359ad2d6 Merge branch 'dev' into 'master'
v1.0.4 last minute fixes

See merge request atom/AtOM!32
2025-10-13 18:21:25 +02:00
Vincent Riquer
75159a4e2b Hotfix: createindex: fix ID3v1 genre index path 2025-10-13 18:20:54 +02:00
Vincent Riquer
325bdb3c96 Hotfix: missing quote in createindex 2025-10-13 18:20:54 +02:00
Vincent Riquer
2b5bf06e58 Hotfix: commit more often when reading tags 2025-10-13 18:20:54 +02:00
ScriptFanix
3a6c7bfc9c Merge branch 'dev' into 'master'
Bugfix release v1.0.4

Closes #26

See merge request atom/AtOM!30
2025-10-11 22:31:16 +02:00
ScriptFanix
f3e4aed71e Merge branch '26-ffmpeg-tags-are-always-null' into 'dev'
Resolve "ffmpeg tags are always NULL"

See merge request atom/AtOM!29
2025-10-11 22:23:17 +02:00
ScriptFanix
a0756b170c Resolve "ffmpeg tags are always NULL" 2025-10-11 22:23:16 +02:00
Vincent Riquer
1d7a09fcff quickfix: replace calls to date with builtin printf or $EPOCHSECONDS 2025-08-08 20:19:51 +02:00
ScriptFanix
1df976bcae Revert "getInfos::guess skeleton"
This reverts commit 0ea0dbe09d59703a2b468a9c96185a148eebc1aa
2025-04-30 02:45:37 +02:00
Vincent Riquer
0ea0dbe09d getInfos::guess skeleton 2025-04-30 02:41:29 +02:00
ScriptFanix
b61dddcd67 Edit README.md 2025-04-29 23:54:59 +02:00
Vincent Riquer
73652a329b Update changelog 2025-04-16 00:03:23 +02:00
Vincent Riquer
ad6d820496 doc: Update example.cfg
Fixes #23
2025-04-15 23:58:14 +02:00
ScriptFanix
af40b872a7 Merge branch '20-bug-setup-is-not-aware-of-copy_extension' into 'master'
Resolve "BUG: Setup is not aware of `copy_extension`"

Closes #20

See merge request atom/AtOM!26
2025-04-15 21:36:31 +00:00
ScriptFanix
95ce31a54a Resolve "BUG: Setup is not aware of copy_extension" 2025-04-15 21:36:31 +00:00
Vincent Riquer
29e8616ff9 README update URL 2025-04-05 06:28:11 +02:00
Vincent Riquer
9cdce7752a README update URL 2025-04-05 06:20:50 +02:00
Vincent Riquer
f7522e5f34 Merge branch '24-release-country-issues' into 'master'
Resolve "release country issues"

Closes #19, #21, #22, and #24

See merge request atom/AtOM!25
2025-04-03 22:51:20 +00:00
Vincent Riquer
6a888f02e7 Resolve "release country issues" 2025-04-03 22:51:19 +00:00
Vincent Riquer
73900210ad Edit CHANGELOG.md 2025-04-02 23:34:16 +00:00
Vincent Riquer
556ce4d142 Edit CHANGELOG.md 2025-04-02 23:28:46 +00:00
Vincent Riquer
c7bea0d3a6 Merge branch '17-read-and-copy-replaygain' into 'master'
Resolve "Read and copy replaygain"

Closes #17

See merge request atom/AtOM!24
2025-04-02 23:22:18 +00:00
Vincent Riquer
806a0f16a7 FEAT: Read and copy replaygain 2025-04-02 23:22:18 +00:00
Vincent Riquer
89100a2949 Move TODO to #18 2025-04-01 01:13:03 +00:00
Vincent Riquer
6cde6b4ada Merge branch 'fix-infinite-loop' into 'master'
fix infinite loop at end of task list

See merge request atom/AtOM!23
2025-03-15 02:10:54 +00:00
Vincent Riquer
0a9a5335bb fix infinite loop at end of task list 2025-03-15 03:01:27 +01:00
Vincent Riquer
2e3c47071f Merge branch '16-unable-to-saturate-cpus-on-sufficiently-powerfulm-hartdware' into 'master'
Resolve "Unable to saturate CPUs on sufficiently powerfulm hartdware"

Closes #16

See merge request atom/AtOM!21
2025-03-14 03:14:57 +00:00
Vincent Riquer
aaaad2ce9a Resolve "Unable to saturate CPUs on sufficiently powerfulm hartdware" 2025-03-14 03:14:57 +00:00
Vincent Riquer
0fd1fae6dc Merge branch '15-temp-directory-not-created-for-toys' into 'master'
Resolve "Temp directory not created for toys"

Closes #15

See merge request atom/AtOM!20
2025-03-13 23:24:59 +00:00
Vincent Riquer
dd72a6b0ba Resolve "Temp directory not created for toys" 2025-03-13 23:24:59 +00:00
Vincent Riquer
0dd962fb8c Enhancement: slight performance improve
atom: don't call `date` to get the timestamp multiple times in the main loop
2025-02-11 21:50:14 +01:00
Vincent Riquer
042cd72ecd Merge branch '12-update-changelog-for-v1-0-2' into 'master'
Resolve "Update CHANGELOG for v1.0.2"

Closes #12

See merge request atom/AtOM!17
2025-02-10 19:04:14 +00:00
Vincent Riquer
8effee243a Resolve "Update CHANGELOG for v1.0.2" 2025-02-10 19:04:13 +00:00
Vincent Riquer
45433fa214 Merge branch '10-bug-releasecountry-change-should-update-dest-files' into 'master'
Resolve "BUG: releasecountry change should update dest files"

Closes #10

See merge request atom/AtOM!16
2025-02-10 18:57:38 +00:00
Vincent Riquer
92a5ba0234 Resolve "BUG: releasecountry change should update dest files" 2025-02-10 18:57:38 +00:00
Vincent Riquer
b718e34836 Edit CHANGELOG.md 2025-02-10 18:09:46 +00:00
Vincent Riquer
fd39c8bb16 Add CHANGELOG 2025-02-10 18:09:24 +00:00
Vincent Riquer
0b58d11c1e quickfix: parsing tags with soxi can hang on large embedded images 2025-02-10 18:41:32 +01:00
Vincent Riquer
d147cdc49c quickfix: help text indentation 2025-02-10 18:41:00 +01:00
Vincent Riquer
5032bb8739 Merge branch '8-feat-support-for-releasecountry-tag' into 'master'
Resolve "FEAT: support for releasecountry tag"

Closes #8 and #9

See merge request atom/AtOM!15
2025-02-10 00:52:01 +00:00
Vincent Riquer
979280c330 Resolve "FEAT: support for releasecountry tag"
Resolve "Opus (and maybe vorbis?) tags aren't actually read!"
2025-02-10 00:52:00 +00:00
Vincent Riquer
2cf968353f Ignore Makefile.in 2025-02-09 20:34:34 +01:00
Vincent Riquer
7ffbedfebd Merge branch '7-provide-a-proper-way-to-install' into 'master'
Resolve "Provide a proper way to install"

Closes #7

See merge request atom/AtOM!14
2025-02-09 02:25:16 +00:00
Vincent Riquer
a4c293c2f3 Add ./configure and Makefile 2025-02-09 02:25:15 +00:00
Vincent Riquer
c63a902738 Merge branch 'xdg-compliance' into 'master'
xdgUpdate: move config file to .config/AtOM/

Closes #2

See merge request atom/AtOM!10
2025-02-08 21:37:55 +00:00
Vincent Riquer
6c55fa4e69 xdgUpdate: move config file to .config/AtOM/ 2025-02-08 21:37:54 +00:00
Vincent Riquer
79cfcc6948 Merge branch '4-with-rename-pattern-destination-files-can-have-in-db-if-tags-missing' into 'master'
Resolve "With rename pattern, destination files can have "//" in DB if tags missing"

Closes #4

See merge request atom/AtOM!7
2025-01-31 01:16:50 +00:00
Vincent Riquer
8c054e6025 Resolve "With rename pattern, destination files can have "//" in DB if tags missing" 2025-01-31 01:16:50 +00:00
Vincent Riquer
68162e8b95 FIX copy extensions regex 2025-01-30 03:25:03 +01:00
Vincent Riquer
91939c0614 FIX: copy_extension
declare hash array
2025-01-30 03:22:53 +01:00
Vincent Riquer
b00f8a5955 Merge branch '6-readme-md-doesn-t-explain-what-this-is-doing' into 'master'
Resolve "README.md doesn't explain what this is doing"

Closes #6

See merge request atom/AtOM!13
2025-01-30 02:09:54 +00:00
Vincent Riquer
c085bcb04b Resolve "README.md doesn't explain what this is doing" 2025-01-30 02:09:53 +00:00
Vincent Riquer
c282e9c361 Merge branch '5-looks-like-too-many-tasks-fail' into 'master'
Resolve "Looks like too many tasks fail"

Closes #5

See merge request atom/AtOM!11
2025-01-28 22:34:20 +00:00
Vincent Riquer
37e149c1b3 Resolve "Looks like too many tasks fail" 2025-01-28 22:34:19 +00:00
Vincent Riquer
434152daba Merge branch 'get-rid-of-transogg' into 'master'
Delete transogg

See merge request atom/AtOM!12
2025-01-28 02:03:31 +00:00
Vincent Riquer
574f70918b Delete transogg 2025-01-28 02:03:30 +00:00
52 changed files with 1504 additions and 1247 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.ex
*.EX
trace.log
.vscode/
.vscode/
Makefile.in

34
CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
# 1.0.3
## `BREAKING CHANGES`
* Implementing replaygain copy meant bumping versions of every tag parser. All file tags will be read again. Running in batches (`-B <batchsize>`) is recommended.
### Bugs
* Fix a number of issues with `releasecountry` tag
### Enhancements
* Spawn tasks faster when slots are available.
* Copy replaygain tags.
* Add `copy_extension` to setup
# 1.0.2
### Bugs
* Tag reading hangs on vorbis files with embedded images.
* Recreate destination files on releasecountry tag change (bump database schema to 5).
# 1.0.1
## `BREAKING CHANGES`
* Implementing releasecountry meant bumping versions of every tag parser. All file tags will be read again. Running in batches (`-B <batchsize>`) is recommended.
### Enhancements
* Copy releasecountry tag ("MusicBrainz Album Release Country" for MP3).
### Bugs
* Opus tags were not actually parsed due to an issue with the tag reader selector.
### Enhancements
* Fetch releasecountry tag from files
* Add `%{releasecountry}` placeholder in `rename`
* Add `-r` (show release country) to toys/createindex
# 1.0.0
Initial public release

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
include Makefile.in
all: atom #$(wildcard toys/*) $(wildcard lib/*/*)
mkdir work
cp -r atom toys work
sed -i 's:%LIBDIR%:'$(libdir)':;s:%SHAREDIR%:'$(sharedir)':;s:%DOCDIR%:'$(docdir)':' work/atom work/toys/*
install:
install -m 644 -D -t $(docdir) doc/*
install -m 644 -D README.md $(docdir)/README.md
install -m 644 -D work/toys/README $(docdir)/README.toys
rm work/toys/README
install -m 644 -D -t $(sharedir) share/*
install -d $(libdir)
cp -dpr --no-preserve=ownership lib/* $(libdir)
install -D -t $(bindir) work/atom work/toys/*
clean:
rm -Rf work

View File

@ -1,9 +1,38 @@
# AtOM: Anything to Ogg and Mp3
* URL: https://framagit.org/ScriptFanix/AtOM/
This program is for you if
1. you want to have your music collection in the highest quality possible
2. you have devices that don't have enough storage to hold it or
3. you have devices that support a limited number of formats
To satisfy that need, we take a "source" directory, inventory and read tags for
every file therein. From that, we can create and maintain copies of said
directory in other formats or quality. There is also the possibility of
ignoring or simply copying files with certain mime-types or extensions.
We try to do this in a *smart* way, by only treating files that are new or
changed since the last run.
Apart from transcoding from one format to another, AtOM can also change
sample-rate, bitrate, and the number of channels! Say, for example, that you
want to stream your music through icecast. In addition to having all the files
in the same format, it will want a constant sample-rate and bitrate. You can
have AtOM do that!
Here's what I have for my tests:
| Directory | Format | Sample rate | Bitrate | Channels | FAT32 compat. | ASCII | Size |
| --------- | ------ | ----------- | ------- | -------- | ------------- | ----- | ---- |
| 0-Full | Mixed | Mixed | Mixed | Mixed | No | No | 508G |
| 1-High | Opus | Same | 128 | Same | Yes | No | 101G |
| 2-Medium | Opus | Same | 64 | Same | Yes | No | 59G |
| 3-Small | Opus | Same | 32 | Same | Yes | No | 30G |
| 4-MP3 | MP3 | 44100 | 128 | 2 | Yes | Yes | 114G |
* URL: https://framagit.org/atom/AtOM/
* Author: Vincent Riquer <vincent+prog.atom@riquer.fr>
* Copyright/left: 2012-2013,2015,2025 Vincent Riquer - GPLv3
- except: transogg: WTFPL 2.0
## Dependencies
### Required:
@ -12,7 +41,7 @@
* [SQLite](http://www.sqlite.org/)
### Optional:
Some features will not be available.
**Some features will be disabled** when not present.
* [vorbis-tools](http://www.vorbis.com/)
* `ogginfo` (Ogg Vorbis metadata)
* `oggenc` (Ogg Vorbis encoding)
@ -32,6 +61,13 @@ Some features will not be available.
## Using the software
### Installation
1. Clone the repository: `git clone https://framagit.org/atom/AtOM.git`
2. run `./configure && make && sudo make install`
`./configure` takes an optional prefix: `./configure --prefix=/usr`. Defaults to /usr/local.
### Configuration:
On first run, AtOM will ask a set of questions to help you create a
configuration file.
@ -42,7 +78,7 @@ If, however, you still want to make changes manually, please read doc/config.
There are a lot of comments in the generated config file too.
### Preparing data:
Nothing specific needs to be done. You can edit ypur tags, rename files, move
Nothing specific needs to be done. You can edit your tags, rename files, move
them around how you see fit. However, make sure you setup your tag editor
to *do* update the files' timestamps: though it was initially plan to make this
optional, using checksums or tags, it was abandoned due to the huge amount of

5
TODO
View File

@ -1,5 +0,0 @@
Tag Guessing
------------
From a user-defined pattern, guess tags for unsupported formats/untagged files
from the file path and file name.

278
atom
View File

@ -18,6 +18,7 @@ declare -A \
destinationchannels \
destinationfat32compat \
destinationcopymime \
destinationcopyext \
destinationformat \
destinationfrequency \
destinationid \
@ -36,17 +37,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -58,13 +57,22 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
help() {
cat <<-EOF
Options:
-c <file> Load configuration file <file>
-C Dump configuration and exit
-l <load> Override max-load
-f <workers> Use exactly <workers> child processes
-f <workers> Use exactly <workers> child processes
-T <seconds> override load-interval
-F <destination> Force re-generation of all files in
<destination>
@ -77,7 +85,6 @@ help() {
}
#parse arguments
OPTERR=0
while getopts ':c:Cl:T:F:f:B:ShDq' opt
do
case $opt in
@ -176,163 +183,7 @@ set +H
(( cfgdump )) && exit
# check sanity
if [ ! -d "$tempdir" ] && ! mkdir -p "$tempdir"
then
echo "[FATAL] Could not create temp directory $tempdir" >&2
(( sanityfail++ ))
fi
if [ ! -f "$database" ] && [ ! -d "${database%/*}" ] && ! mkdir -p "${database%/*}"
then
echo "[FATAL] Directory holding database file does not exist and could" \
"not be created" >&2
(( sanityfail++ ))
fi
if [ ! -d "$sourcepath" ]
then
echo "[FATAL] Source path $sourcepath does not exist or is not a directory" >&2
(( sanityfail++ ))
fi
if ! which sed >/dev/null
then
echo "[FATAL] Required tool sed is not installed or not in PATH
I never thought this would actually hit someone..." >&2
(( sanityfail++ ))
fi
if ! which sox >/dev/null
then
echo "[FATAL] Required tool sox is not installed or not in PATH" >&2
(( sanityfail++ ))
fi
if ! which ogginfo >/dev/null
then
echo "[WARNING] Tool ogginfo (from vorbis-tools) is not" \
"installed or not in PATH
WebM metadata disabled" >&2
disableogginfo=1
(( sanitywarn++ ))
fi
if ! which soxi >/dev/null
then
echo "[WARNING] Tool soxi (from sox) is not" \
"installed or not in PATH
Vorbis metadata disabled" >&2
disablesoxi=1
(( sanitywarn++ ))
fi
if (( oggencneeded )) && ! which oggenc >/dev/null
then
echo "[WARNING] Tool oggenc (from vorbis-tools) is not" \
"installed or not in PATH
Vorbis targets disabled" >&2
disableoggenc=1
(( sanitywarn++ ))
fi
if ! which opusinfo >/dev/null
then
echo "[WARNING] Tool opusinfo (from opus-tools) is not" \
"installed or not in PATH
Opus metadata disabled" >&2
disableopusinfo=1
(( sanitywarn++ ))
fi
if (( opusencneeded )) && ! which opusenc >/dev/null
then
echo "[WARNING] Tool opusenc (from opus-tools) is not" \
"installed or not in PATH
Opus targets disabled" >&2
disableopusenc=1
(( sanitywarn++ ))
fi
if ! which opusdec >/dev/null
then
echo "[WARNING] Tool opusdec (from opus-tools) is not" \
"installed or not in PATH
Opus support disabled" >&2
disableopusdec=1
(( sanitywarn++ ))
fi
if (( lameneeded )) && ! which lame >/dev/null
then
echo "[WARNING] Tool lame is not installed or not in PATH
MP3 targets disabled" >&2
disablelame=1
(( sanitywarn++ ))
fi
if ! which metaflac >/dev/null
then
echo "[WARNING] Tool metaflac (from FLAC) is not installed" \
"or not in PATH
FLAC metadata disabled" >&2
disableflac=1
(( sanitywarn++ ))
fi
if ! which mpcdec >/dev/null
then
echo "[WARNING] Tool mpcdec (from Musepack) is not" \
"installed or not in PATH
Musepack support disabled" >&2
disablempcdec=1
(( sanitywarn++ ))
fi
if ! which mkvextract >/dev/null
then
echo "[WARNING] Tool mkvextract (from MKVToolNix) is not" \
"installed or not in PATH
WebM metadata disabled
WebM support disabled" >&2
disablemkvextract=1
(( sanitywarn++ ))
fi
if ! which ffprobe >/dev/null
then
echo "[WARNING] Tool ffprobe (from FFmpeg) is not installed or not in PATH
Video metadata disabled
MPEG metadata disabled
MusePack metadata disabled
Unknown format metadata disabled" >&2
disableffprobe=1
(( sanitywarn++ ))
fi
if ! which ffmpeg >/dev/null
then
echo "[WARNING] Tool ffmpeg is not installed or not in PATH
Video support disabled" >&2
disablevideo=1
(( sanitywarn++ ))
fi
if (( textunidecodeneeded )) && ! perl -MText::Unidecode -e 'exit;' 2>/dev/null
then
echo "[WARNING] Perl module Text::Unidecode is not available
Renaming to ASCII-only disabled" >&2
unset destinationascii
destinationascii=0
textunidecodeneeded=0
(( sanitywarn++ ))
fi
if (( sanityfail ))
then
echo "
Sanity checks raised ${sanitywarn:-0} warnings, $sanityfail failures. Dying now." >&2
exit $ESANITY
elif (( sanitywarn ))
then
echo "
Sanity checks raised $sanitywarn warnings... Hit Control-C to abort." >&2
if ! (( cron ))
then
timeout=$(( sanitywarn * 10 ))
echo -n "Starting in $(printf %3i $timeout)" \
$'seconds...\b\b\b\b\b\b\b\b\b\b\b' >&2
while (( timeout ))
do
echo -n $'\b\b\b'"$(printf %3i $timeout)" >&2
sleep 1
(( timeout-- ))
done
echo -en "\r\033[K"
fi
fi
sanityCheck
openDatabase
for destination in "${destinations[@]}"
@ -429,7 +280,7 @@ echo '
CREATE TEMPORARY TABLE tasks(
id INTEGER PRIMARY KEY,
requires INTEGER,
required INTEGER,
required_by INTEGER DEFAULT 0,
status INTEGER NOT NULL,
key TEXT UNIQUE,
rename_pattern TEXT,
@ -547,20 +398,23 @@ echo '
mime_type_actions.mime_text,
destinations.name,
destination_files.id,
tags.depth,
tags.rate,
tags.channels,
tags.bitrate,
tags.genre,
tags.albumartist,
tags.year,
tags.album,
tags.disc,
tags.albumartist,
tags.artist,
tags.track,
tags.title,
tags.bitrate,
tags.channels,
tags.composer,
tags.performer
tags.depth,
tags.disc,
tags.genre,
tags.performer,
tags.rate,
tags.releasecountry,
tags.replaygain_alb,
tags.replaygain_trk,
tags.title,
tags.track,
tags.year
FROM source_files
INNER JOIN destination_files
ON source_files.id
@ -603,33 +457,39 @@ do
rest=${rest#*::AtOM:SQL:Sep::}
destfileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
bitdepth=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
rate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
channels=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
bitrate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
genre=${rest%%::AtOM:SQL:Sep::*}
album=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
albumartist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
year=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
album=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
disc=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
artist=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
track=${rest%%::AtOM:SQL:Sep::*}
bitrate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
title=${rest%%::AtOM:SQL:Sep::*}
channels=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
composer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
depth=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
disc=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
genre=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
performer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
rate=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
releasecountry=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
replaygain_alb=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
replaygain_trk=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
title=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
track=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
year=${rest%%::AtOM:SQL:Sep::*}
unset rest
case ${destinationformat["$destination"]} in
vorbis) (( disableoggenc )) && continue ;;
@ -641,7 +501,7 @@ do
getDestFile
for copy_ext in "${destinationcopyext[@]}"
do
if [[ $filename =~ '.*\.'$copy_ext'$' ]]
if [[ $filename =~ '.*\.'"$copy_ext"'$' ]]
then
copied=1
break
@ -657,13 +517,13 @@ do
album \
albumartist \
artist \
bitdepth \
bitrate \
channels \
commandline \
composer \
copied \
decodetaskid \
depth \
destfileid \
destination \
disc \
@ -672,6 +532,9 @@ do
mimetype \
performer \
rate \
releasecountry \
replaygain_alb \
replaygain_trk \
rest \
sox_needed \
soxoptions_in \
@ -692,19 +555,20 @@ echo "Created ${count:-0} tasks for $filecount files ${togo:+($togo left) }(${co
concurrency=$(( maxload / 2 ))
(( concurrency )) || concurrency=1
active=0
concurrencychange=$(date +%s)
concurrencychange=$EPOCHSECONDS
starttime=$concurrencychange
taskcount=$count
remaining=$taskcount
failed=0
echo 'BEGIN TRANSACTION;' >&3
committime=$(date +%s)
committime=$EPOCHSECONDS
while (( (remaining || ${#workers[@]}) && ! quit ))
do
if (( $(date +%s) - committime >= 60 ))
timestamp=$EPOCHSECONDS
if (( $timestamp - committime >= 60 ))
then
echo $'COMMIT;\nBEGIN TRANSACTION;' >&3
committime=$(date +%s)
committime=$timestamp
fi
read humanload garbage < /proc/loadavg
load=${humanload%.*}
@ -714,16 +578,16 @@ do
else
if [ -z "$quit" ] \
&& (( ! pause )) \
&& (( $(date +%s)-concurrencychange >= loadinterval ))
&& (( timestamp - concurrencychange >= loadinterval ))
then
if (( concurrency > 1 )) \
&& (( load > maxload ))
then
concurrencychange=$(date +%s)
concurrencychange=$timestamp
(( --concurrency ))
elif (( load < maxload )) && (( active > concurrency - 1 ))
then
concurrencychange=$(date +%s)
concurrencychange=$timestamp
(( ++concurrency ))
fi
fi
@ -733,7 +597,7 @@ do
(( pause )) || master
if (( ran - failed ))
then
currenttime=$(date +%s)
currenttime=$timestamp
if (( pause ))
then
(( runtime = pausestart - starttime - pausedtime ))
@ -809,7 +673,7 @@ done
echo 'COMMIT;' >&3
unset count
endtime=$(date +%s)
endtime=$EPOCHSECONDS
(( elapsedseconds = endtime - starttime - pausedtime ))
(( days =
@ -906,8 +770,7 @@ then
FROM tasks
INNER JOIN source_files
ON tasks.source_file=source_files.id
WHERE tasks.status = 2
AND requires is NULL;
WHERE tasks.status = 2;
SELECT "AtOM:NoMoreFiles";' >&3
read -u4 line
@ -946,6 +809,7 @@ do
tags.disc,
tags.genre,
tags.performer,
tags.releasecountry,
tags.title,
tags.track,
tags.year
@ -1014,6 +878,8 @@ do
rest=${rest#*::AtOM:SQL:Sep::}
performer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
releasecountry=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
title=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
track=${rest%%::AtOM:SQL:Sep::*}

28
configure vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# defaults
default_prefix=/usr/local
#default_bindir=$default_prefix/bin
#default_libdir=$default_prefix/lib
#default_sharedir=$default_prefix/share
while (( $# ))
do
case "$1" in
--prefix=*) prefix="${1#*=}"
;;
esac
shift
done
bindir="${prefix:-$default_prefix}"/bin
libdir="${prefix:-$default_prefix}"/lib/AtOM
sharedir="${prefix:-$default_prefix}"/share/AtOM
docdir="${prefix:-$default_prefix}"/share/doc/AtOM
cat > Makefile.in <<-EOMakefile.in
bindir = "$bindir"
libdir = "$libdir"
sharedir = "$sharedir"
docdir = "$docdir"
EOMakefile.in

View File

@ -1,55 +1,186 @@
[general]
ionice 3
max-load 6
load-interval 30
temporary-directory %HOME%/.atom/tmp
database %HOME%/.atom/atom.db
debug 0
# This section contains parameters of the program itself.
# * max-load <load>: Integer. Defines how parallel processing will behave. AtOM
# will try to keep the 1 minute load average between <load> and <load>+1 by
# adjusting concurrency.
# Initial concurrency will be set to half of that value.
max-load 16
# * load-interval <seconds>: Integer. How often should we check the load average
# and adjust concurrency. Set this too low, and concurrency may be increased
# too quickly. Set this too high, and AtOM will not adapt quickly enough to
# load increase. In both cases, your hard drive will suffer. In my
# experience, 30 seconds is a good value.
load-interval 10
# * ionice <class> [niceness]: IO-hungry processes will be run with ionice class
# <class> and niceness [niceness] (if applicable). See man ionice for details.
ionice 3
# * temporary-directory <directory>: String. Name speaks for itself: this is
# where FIFOs (for communicating with sqlite) and temporary WAVE files will
# be created. Note that debug logs (if enabled) will go there too.
temporary-directory /tmp/AtOM-user/
# * database <file>: String. Where the SQLite database should be stored.
database /home/user/.local/share/AtOM/atom.db
# * debug <level>: Integer.
# 0: No debug output, encoding and decoding errors logged to
# <temporary-directory>/workerN.log
# 1: Print some debug to stdout, log decoding and encoding commands to
# <temporary-directory>/workerN.log
# 3: log SQL queries to <temporary-directory>/debug.log
#debug 1
[source]
path /var/lib/mpd/music
skip /last
skip /lastfm
skip /zzz-atrier
# This section defines where are the files you want transcoded.
[Ogg]
path /mnt/Musique-OggQ2
format vorbis
quality 1
normalize yes
channels 2
frequency 44100
# * path <directory>: String. The root of your collection.
# Default: /var/lib/mpd/music
path /mnt/Musique
# * skip <directory>: String. Files in <directory> will be ignored. Note that
# <directory> can be any expression accepted by find.
skip /lost+found
skip /last
skip /lastfm
skip /zzz-atrier
[Vorbis]
# Each section not named 'general' or 'source' will define a new destination.
# Common parameters:
# Mandatory parameters:
# * enabled: Whether or not to treat this destination (1=true/0=false)
enabled 1
# * path: Where files will be written
path /mnt/Musique-OggQ2
# * format: copy, ogg, opus or mp3. Other formats may appear in the future -
# feel free to implement your preferred format.
format vorbis
# Ogg parameters:
# * quality <quality>: The quality parameter of oggenc. See man oggenc for
# more info. This is the only mode supported and planned. Still, if you want
# to be able to use bitrate settings, feel free to fork and file a pull
# request.
quality 1
# Optional parameters:
# * normalize <yes>/<no>: Normalize output files.
normalize yes
# * rename <string>: Destination files will be named according to <string>,
# after expansion of special strings:
# %{album},
# %{albumartist},
# %{artist},
# %{disc},
# %{genre},
# %{releasecountry},
# %{title},
# %{track},
# %{year}.
# Untagged files or files in unrecognized formats will not be changed.
rename %{genre}/%{albumartist}/%{year}-%{album}-%{releasecountry}/%{disc}%{track}--%{artist}-%{title}
# * fat32compat <yes>/<no>: Rename files for compatibility with FAT32
# filesystems.
fat32compat yes
# * ascii-only <yes>/<no>: Rename files for compatibility with ASCII-only
# systems.
ascii-only no
# you should not skip or copy application/octet-stream, they could be something
# similar to "Audio file with ID3 version 2.4.0, unsynchronized frames"
copy_mime-type image/*
copy_mime-type text/*
# * skip_mime-type <mime-type>: Files with mime-type <mime-type> will not
# be included in that destination. For more than one mime-type, use multiple
# times, as needed. The '*' character is a wildcard.
skip_mime-type inode/x-empty
skip_mime-type audio/midi
skip_mime-type image/*
skip_mime-type text/*
skip_mime-type application/pdf
skip_mime-type application/javascript*
skip_mime-type very short file (no magic)
# * copy_mime-type <mime-type>: Same as skip_mime-type, except that files
# matching will be copied as-is to the destination. E.g. image/* will copy
# covers and other images to the destination. In fact, AtOM will try to use
# hard links instead of copies.
copy_mime-type image/*
# * copy_extension <extension>: Copy files whose name and with ".<extension>"
copy_extension txt
# * channels <number>: Files with more than <number> channels will be
# downmixed. Useful if you create files for telephony music-on-hold.
channels 2
# * frequency <hertz>: Files will be resampled as needed to <hertz>Hz
# sampling-rate. Shoutcast/Icecast streams require a constant sampling-rate.
# Telephony systems often require a sample rate of 8000Hz.
frequency 44100
# * higher-than <bitrate>: Integer. Only reencode files with bitrates higher
# then <bitrate>kbps. This only applies if sample-rate, channel count and of
# course format are equal. If unset, only files with bitrates equal to that
# of the target will be copied (actually, hardlinking will be attempted
# first). As Ogg Vorbis target quality is not defined by its bitrate, Ogg
# Vorbis files will always be reencoded if unset.
higher-than 200
[Opus]
enabled 1
path /mnt/Musique-opus
format opus
# Opus parameters:
# * bitrate <bitrate>: Set (VBR) bitrate to <bitrate>. Note that while Opus
# allows for decimal values, AtOM does not. The reason for this is simple:
# we do numeric comparisons, and Bash only manipulates integers.
bitrate 96
normalize yes
frequency 48000
copy_mime-type image/*
copy_mime-type text/*
[MP3]
path /mnt/Musique-mp3.test
enabled 1
path /mnt/Musique-mp3
format mp3
# MP3 parameters:
# * bitrate <bitrate>: Set ABR to <bitrate>. Again, if you want CBR or any
# other mode supported by lame, please fork and file a pull request.
bitrate 96
# * noresample <yes>/<no>: LAME may decide to encode your file to a lower
# sampling-rate if you use a low bitrate. Setting this to yes will
# append --resample <original file's rate>, preventing any resampling from
# happening.
noresample yes
normalize yes
higher-than 128
# rename file, path unchanged
rename %{track}--%{artist}-%{title}
# change the whole filepath
#rename %{genre}/%{albumartist}/%{year}-%{album}/%{track}--%{artist}-%{title}
#rename %{genre}/%{albumartist}/%{year}-%{album}-%{releasecountry}/%{track}--%{artist}-%{title}
skip_mime-type image/*
skip_mime-type text/*
[asterisk]
enabled 1
path /mnt/Musique-asterisk
format vorbis
quality 0

View File

@ -49,13 +49,13 @@ printConfig() {
EOF
[ -n "${destinationskipmime["$destination"]}" ] \
&& echo " |Skipped mime-types|${destinationskipmime["$destination"]//\|/
| | |}"
| |}"
[ -n "${destinationcopymime["$destination"]}" ] \
&& echo " |Copied mime-types|${destinationcopymime["$destination"]//\|/
| | |}"
| |}"
[ -n "${destinationcopyext["$destination"]}" ] \
&& echo " |Copied extensions|${destinationcopyext["$destination"]//\|/
| | |}"
| |}"
done
}|column -t -s'|'
}

View File

@ -60,7 +60,7 @@ path $sourcepath
# Common parameters:
# Mandatory parameters:
# * enabled: Whether or not to treat this destination (1=tue/0=false)
# * enabled: Whether or not to treat this destination (1=true/0=false)
enabled 1
# * path: Where files will be written
@ -144,6 +144,7 @@ bitrate ${destinationquality["$destination"]}
# %{artist},
# %{disc},
# %{genre},
# %{releasecountry},
# %{title},
# %{track},
# %{year}.
@ -207,8 +208,7 @@ bitrate ${destinationquality["$destination"]}
done
cat <<-EOCfg
# * copy_extension <extension>: Same as skip_extension, except that files
# matching will be copied as-is to the destination.
# * copy_extension <extension>: Copy files whose name and with ".<extension>"
EOCfg
destinationcopyext["$destination"]="${destinationcopyext["$destination"]}|"
while [[ ${destinationcopyext["$destination"]} =~ \| ]]

View File

@ -1,5 +1,5 @@
#!/bin/bash
currentdbversion=3
#!/usr/bin/env bash
currentdbversion=6
checkDatabaseVersion() {
local dbversion
if dbversion=$(Select atom version <<<"\"1\" = 1")

View File

@ -1,5 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash
openDatabase() {
[[ -f "$database" ]] || populate_db=1
rm -f "$tempdir"/sqlite.{in,out}
mkfifo "$tempdir"/sqlite.{in,out}
sqlite3 -bail "$database" \
@ -11,9 +12,9 @@ openDatabase() {
if (( debug > 2 ))
then
exec 5>&3
exec 3> >(tee -a $tempdir/debug.log >&5)
exec 3> >(tee -a "$tempdir/debug.log" >&5)
fi
cat $schema >&3
(( populate_db )) && cat $schema >&3
echo '.separator ::AtOM:SQL:Sep::' >&3
echo 'PRAGMA foreign_keys = ON;' >&3
echo 'PRAGMA recursive_triggers = ON;' >&3

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
upgradedatabase_1_2() {
local data \

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
upgradedatabase_2_3() {
local data \
@ -11,4 +11,4 @@ upgradedatabase_2_3() {
Update destinations enabled 1 <<< "1 = 1"
Update atom version 3 <<<"1 = 1"
}
}

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
upgradedatabase_3_4() {
echo "Upgrading database to version 4... (backup is $database.bak_v3)"
cp "$database" "$database.bak_v3"
echo 'ALTER TABLE tags ADD COLUMN releasecountry TEXT;' >&3
Update atom version 4 <<<"1 = 1"
}

View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
upgradedatabase_4_5() {
echo "Upgrading database to version 5... (backup is $database.bak_v4)"
cp "$database" "$database.bak_v4"
echo 'DROP TRIGGER force_destination_update_on_tag_update;' >&3
echo '
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
AFTER UPDATE OF
genre,
albumartist,
year,
album,
disc,
artist,
track,
title,
composer,
performer,
releasecountry,
rate,
channels,
bitrate,
depth
ON tags
BEGIN
UPDATE destination_files SET last_change=0
WHERE source_file_id=old.source_file;
END;
' >&3
Update atom version 5 <<<"1 = 1"
}

View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
upgradedatabase_5_6() {
echo "Upgrading database to version 6... (backup is $database.bak_v5)"
cp "$database" "$database.bak_v5"
echo '
ALTER TABLE tags ADD COLUMN replaygain_alb TEXT;
ALTER TABLE tags ADD COLUMN replaygain_trk TEXT;
DROP TRIGGER force_destination_update_on_tag_update;
CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
AFTER UPDATE OF
genre,
albumartist,
year,
album,
disc,
artist,
track,
title,
composer,
performer,
releasecountry,
replaygain_alb,
replaygain_trk,
rate,
channels,
bitrate,
depth
ON tags
BEGIN
UPDATE destination_files SET last_change=0
WHERE source_file_id=old.source_file;
END;
' >&3
Update atom version 6 <<<"1 = 1"
}

View File

@ -153,18 +153,24 @@ decodeFile() {
done
)
status 0
cleanup $tmpfile.wav
EOInsert
)
progressSpin
fi
if (( sox_needed ))
then
cleanup="$tempdir/$tmpfile"
decodeSox "$tempdir/$tmpfile.wav"
if ! soxtaskid=$(
Select tasks id <<<"key = $tmpfile"
)
then
parent_required=$(
Select tasks required_by \
<<<"id = $decodetaskid"
)
Update tasks required_by $((++parent_required)) \
<<<"id = $decodetaskid"
soxtaskid=$(
Insert tasks <<-EOInsert
key $tmpfile
@ -176,9 +182,8 @@ decodeFile() {
done
)
requires $decodetaskid
required $decodetaskid
status 0
cleanup $cleanup
cleanup $tmpfile.wav
EOInsert
)
progressSpin

View File

@ -26,7 +26,7 @@ decodeSox() {
commandline+=(-c ${destinationchannels["$destination"]})
soxoptions_out+=" -c ${destinationchannels["$destination"]}"
fi
if (( ${bitdepth:-0} > 16 ))
if (( ${depth:-0} > 16 ))
then
commandline+=(-b 16)
soxoptions_out+=" -b 16"

View File

@ -1,6 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash
encodeFile::mp3() {
lameopts=(${ionice}lame --quiet)
lameopts=(${ionice}lame --quiet --noreplaygain)
lameopts+=(-v --abr ${destinationquality[$destination]})
[ -n "$album" ] && lameopts+=(--tl "$album" )
[ -n "$artist" ] && lameopts+=(--ta "$artist")
@ -11,6 +11,12 @@ encodeFile::mp3() {
[ -n "$albumartist" ] && lameopts+=(--tv TPE2="$albumartist")
[ -n "$composer" ] && lameopts+=(--tv TCOM="$composer")
[ -n "$performer" ] && lameopts+=(--tv TOPE="$performer")
[ -n "$releasecountry" ] \
&& lameopts+=(--tv TXXX="MusicBrainz Album Release Country=$releasecountry")
[ -n "$replaygain_alb" ] \
&& lameopts+=(--tv "TXXX=REPLAYGAIN_ALBUM_GAIN=$replaygain_alb")
[ -n "$replaygain_trk" ] \
&& lameopts+=(--tv "TXXX=REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
[ -n "$disc" ] && lameopts+=(--tv TPOS="$disc")
if (( ${destinationnoresample[$destination]:-0} == 1 ))
then
@ -52,7 +58,6 @@ encodeFile::mp3() {
Insert tasks <<-EOInsert
key ${fileid}lame$destination
requires ${soxtaskid:-$decodetaskid}
required ${soxtaskid:-$decodetaskid}
fileid $destfileid
filename $destdir/$destfile.mp3
$(
@ -66,7 +71,6 @@ encodeFile::mp3() {
echo "cmd_arg$key $cleanedopts"
done
)
cleanup $tempdir/$tmpfile.wav
source_file $fileid
status 0
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}
@ -74,6 +78,12 @@ encodeFile::mp3() {
ascii ${destinationascii["$destination"]}
EOInsert
)
parent_required=$(
Select tasks required_by \
<<<"id = ${soxtaskid:-$decodetaskid}"
)
Update tasks required_by $((++parent_required)) \
<<<"id = ${soxtaskid:-$decodetaskid}"
progressSpin
soxtaskid=''
}

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
encodeFile::opus() {
opusencopts=(${ionice}opusenc --quiet)
opusencopts+=(--bitrate ${destinationquality[$destination]})
@ -11,6 +11,14 @@ encodeFile::opus() {
[ -n "$disc" ] && opusencopts+=(--comment "DISCNUMBER=$disc")
[ -n "$genre" ] && opusencopts+=(--comment "GENRE=$genre")
[ -n "$performer" ] && opusencopts+=(--comment "PERFORMER=$performer")
[ -n "$releasecountry" ] \
&& opusencopts+=(--comment "RELEASECOUNTRY=$releasecountry")
[ -n "$replaygain_alb" ] \
&& opusencopts+=(--comment) \
&& opusencopts+=("REPLAYGAIN_ALBUM_GAIN=$replaygain_alb")
[ -n "$replaygain_trk" ] \
&& opusencopts+=(--comment) \
&& opusencopts+=("REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
[ -n "$title" ] && opusencopts+=(--title "$title")
[ -n "$track" ] && opusencopts+=(--comment "TRACKNUMBER=${track%/*}")
[ -n "${track#*/}" ] && opusencopts+=(--comment "TRACKTOTAL=${track#*/}")
@ -20,7 +28,6 @@ encodeFile::opus() {
Insert tasks <<-EOInsert
key ${fileid}opusenc$destination
requires ${soxtaskid:-$decodetaskid}
required ${soxtaskid:-$decodetaskid}
fileid $destfileid
filename $destdir/$destfile.opus
$(
@ -34,7 +41,6 @@ encodeFile::opus() {
echo "cmd_arg$key $cleanedopts"
done
)
cleanup $tempdir/$tmpfile.wav
source_file $fileid
status 0
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}
@ -42,6 +48,12 @@ encodeFile::opus() {
ascii ${destinationascii["$destination"]}
EOInsert
)
parent_required=$(
Select tasks required_by \
<<<"id = ${soxtaskid:-$decodetaskid}"
)
Update tasks required_by $((++parent_required)) \
<<<"id = ${soxtaskid:-$decodetaskid}"
progressSpin
soxtaskid=''
}

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
encodeFile::vorbis() {
oggencopts=(${ionice}oggenc -Q -q ${destinationquality[$destination]})
[ -n "$albumartist" ] && oggencopts+=(-c "ALBUMARTIST=$albumartist")
@ -8,6 +8,12 @@ encodeFile::vorbis() {
[ -n "$disc" ] && oggencopts+=(-c "DISCNUMBER=$disc")
[ -n "$genre" ] && oggencopts+=(-G "$genre")
[ -n "$performer" ] && oggencopts+=(-c "PERFORMER=$performer")
[ -n "$releasecountry" ] \
&& oggencopts+=(-c "RELEASECOUNTRY=$releasecountry")
[ -n "$replaygain_alb" ] \
&& oggencopts+=(-c "REPLAYGAIN_ALBUM_GAIN=$replaygain_alb")
[ -n "$replaygain_trk" ] \
&& oggencopts+=(-c "REPLAYGAIN_TRACK_GAIN=$replaygain_trk")
[ -n "$title" ] && oggencopts+=(-t "$title")
[ -n "$track" ] && oggencopts+=(-N "$track")
[ -n "$year" ] && oggencopts+=(-d "$year")
@ -16,7 +22,6 @@ encodeFile::vorbis() {
Insert tasks <<-EOInsert
key ${fileid}oggenc$destination
requires ${soxtaskid:-$decodetaskid}
required ${soxtaskid:-$decodetaskid}
fileid $destfileid
filename $destdir/$destfile.ogg
$(
@ -30,7 +35,6 @@ encodeFile::vorbis() {
echo "cmd_arg$key ${oggencopts[key]}"
done
)
cleanup $tempdir/$tmpfile.wav
source_file $fileid
status 0
rename_pattern ${destinationrenamepath[$destination]}/${destinationrename[$destination]}
@ -38,6 +42,12 @@ encodeFile::vorbis() {
ascii ${destinationascii["$destination"]}
EOInsert
)
parent_required=$(
Select tasks required_by \
<<<"id = ${soxtaskid:-$decodetaskid}"
)
Update tasks required_by $((++parent_required)) \
<<<"id = ${soxtaskid:-$decodetaskid}"
progressSpin
soxtaskid=''
}

View File

@ -10,6 +10,10 @@ getDestDir() {
[[ ${destinationrenamepath[$destination]} == \
*?([^[])%\{albumartist\}?([^\]])* ]] \
&& [ -n "$albumartist" ]
) || (
[[ ${destinationrenamepath[$destination]} == \
*?([^[])%\{releasecountry\}?([^\]])* ]] \
&& [ -n "$releasecountry" ]
) || (
[[ ${destinationrenamepath[$destination]} == \
*?([^[])%\{artist\}?([^\]])* ]] \
@ -44,6 +48,8 @@ getDestDir() {
read -r -u${toascii[0]} album
echo "$albumartist" >&${toascii[1]}
read -r -u${toascii[0]} albumartist
echo "$releasecountry" >&${toascii[1]}
read -r -u${toascii[0]} releasecountry
echo "$artist" >&${toascii[1]}
read -r -u${toascii[0]} artist
echo "$genre" >&${toascii[1]}
@ -61,6 +67,8 @@ getDestDir() {
destdir+="${destinationrenamepath[$destination]//?(\[)%\{album\}?(\])/$replace}"
replace=$(sanitizeFile "$albumartist" dir)
destdir="${destdir//?(\[)%\{albumartist\}?(\])/$replace}"
replace=$(sanitizeFile "$releasecountry" dir)
destdir="${destdir//?(\[)%\{releasecountry\}?(\])/$releasecountry}"
replace=$(sanitizeFile "$artist" dir)
destdir="${destdir//?(\[)%\{artist\}?(\])/$replace}"
replace=$(sanitizeFile "$genre" dir)
@ -86,7 +94,8 @@ getDestDir() {
echo "$thispart" >&${toascii[1]}
read -r -u${toascii[0]} thispart
fi
destdir+="/$(sanitizeFile "$thispart" dir)"
sanitized="$(sanitizeFile "$thispart" dir)"
destdir+="${sanitized:+/}$sanitized"
part=${part#*/}
done
fi

View File

@ -10,6 +10,10 @@ getDestFile() {
[[ ${destinationrename[$destination]} == \
*?([^[])%\{albumartist\}?([^\]])* ]] \
&& [ -n "$albumartist" ]
) || (
[[ ${destinationrenamepath[$destination]} == \
*?([^[])%\{releasecountry\}?([^\]])* ]] \
&& [ -n "$releasecountry" ]
) || (
[[ ${destinationrename[$destination]} == \
*?([^[])%\{artist\}?([^\]])* ]] \
@ -39,6 +43,7 @@ getDestFile() {
then
destfile="${destinationrename[$destination]//?(\[)%\{album\}?(\])/$album}"
destfile="${destfile//?(\[)%\{albumartist\}?(\])/$albumartist}"
destfile="${destfile//?(\[)%\{releasecountry\}?(\])/$releasecountry}"
destfile="${destfile//?(\[)%\{artist\}?(\])/$artist}"
destfile="${destfile//?(\[)%\{genre\}?(\])/$genre}"
destfile="${destfile//?(\[)%\{title\}?(\])/$title}"

View File

@ -1,6 +1,6 @@
#!/bin/bash
getFiles() {
scantime=$(date +%s)
scantime=$EPOCHSECONDS
for prune_expression in "${skippeddirectories[@]}"
do
prunes+=( -path "$sourcepath$prune_expression" -prune -o )

View File

@ -251,6 +251,7 @@ setupDestination() {
%{artist},
%{disc},
%{genre},
%{releasecountry},
%{title},
%{track},
%{year}.
@ -387,16 +388,16 @@ setupDestination() {
copiedmimes+=("${destinationcopymime["$destination"]%%|*}")
destinationcopymime["$destination"]="${destinationcopymime["$destination"]#*|}"
done
[ -n "${destinationcopymime["$destination"]}" ] \
[ -n "${destinationcopymime["$destination"]}" ] \
&& copiedmimes+=("${destinationcopymime["$destination"]}")
count=${#copiedmimes[@]}
unset destinationcopymime["$destination"]
for (( i=0 ; 1 ; i++ ))
do
read \
-e \
read \
-e \
${copiedmimes[i]+-i"${copiedmimes[i]}"} \
-p 'Copy mime-type: ' \
-p 'Copy mime-type: ' \
value
if [ -n "$value" ]
then
@ -409,6 +410,44 @@ setupDestination() {
fi
done
unset copiedmimes
cat <<-EODesc
Copy extensions (extension, string):
Files with extension <extension> will be copied as-is to the
destination. E.g. .jpg will copy covers and other images to the
destination.
This prompt will loop until an empty string is encountered.
EODesc
while [[ ${destinationcopyext["$destination"]} =~ \| ]]
do
copiedexts+=("${destinationcopyext["$destination"]%%|*}")
destinationcopyext["$destination"]="${destinationcopyext["$destination"]#*|}"
done
[ -n "${destinationcopyext["$destination"]}" ] \
&& copiedexts+=("${destinationcopyext["$destination"]}")
count=${#copiedexts[@]}
unset destinationcopyext["$destination"]
for (( i=0 ; 1 ; i++ ))
do
read \
-e \
${copiedexts[i]+-i"${copiedexts[i]}"} \
-p 'Copy extension: ' \
value
if [ -n "$value" ]
then
destinationcopyext[$destination]="${destinationcopyext[$destination]:+${destinationcopyext[$destination]}|}$value"
elif (( i < count ))
then
continue
else
break
fi
done
unset copiedexts
cat <<-EODesc
Channels (integer):

View File

@ -24,6 +24,7 @@ Completion is available for prompts asking for a paths or filenames.
destinationchannels \
destinationfat32compat \
destinationcopymime \
destinationcopyext \
destinationformat \
destinationfrequency \
destinationid \
@ -40,6 +41,7 @@ Completion is available for prompts asking for a paths or filenames.
destinationchannels \
destinationfat32compat \
destinationcopymime \
destinationcopyext \
destinationformat \
destinationfrequency \
destinationid \
@ -74,7 +76,7 @@ Completion is available for prompts asking for a paths or filenames.
esac
;;
esac
read -p'Run now? [Y/n] ' do_run
read -p'Run index and conversion now? [Y/n] ' do_run
case $do_run in
n) exit ;;
*) ;;

View File

@ -1,55 +0,0 @@
#!/bin/bash
getInfosffmpeg_version='ffmpeg-6'
tagreaders+=( "$getInfosffmpeg_version" )
getInfos::ffmpeg() {
tagreader="$getInfosffmpeg_version"
local allinfos=$(
ffprobe -show_streams \
-i "$sourcepath/$filename" 2>&1 \
|sed '
/^Input/,/.* Audio: /{s/ *: */=/}
s/^[[:space:]]*//
s/\0//g'
)
local metadata=$(
echo -e "$allinfos" \
|sed -n '/Metadata=/,/\[STREAM\]/p'
)
local fmt_infos=$(
echo -e "$allinfos" \
|sed -n \
'/codec_type=audio/,/\[STREAM\]/{
/^\(sample_fmt\|sample_rate\|bit_rate\|channels\)=/{
p
}
}'
)
local infos="$metadata"
albumartist=$(gettag album_artist)
album=$(gettag album)
artist=$(gettag artist)
composer=$(gettag composer)
disc=$(gettag disc)
genre=$(gettag genre)
performer=$(gettag TOPE)
title=$(gettag title)
tracknum=$(gettag track)
year=$(gettag date)
expr='^[0-9]*$'
if [ -n "$genre" ] && [[ $genre =~ $expr ]]
then
genre="${id3genres[$genre]}"
fi
infos="$fmt_infos"
channels=$(gettag channels)
rate=$(gettag 'sample_rate')
bitrate=$(gettag 'bit_rate')
bitdepth=$(gettag 'sample_fmt')
bitdepth=${bitdepth//[A-z]/}
if [[ $bitrate == N/A ]]
then
unset bitrate
else
bitrate=$((bitrate / 1000))
fi
}

View File

@ -1,48 +0,0 @@
#!/bin/bash
getInfosFLAC_version='FLAC-3'
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)
tracktotal=$(gettag tracktotal)
year=$(gettag date)
if [ -n "$tracknum" -a -n "$tracktotal" ]
then
tracknum="$tracknum/$tracktotal"
fi
year=$(gettag DATE)
{
read rate
read channels
read bitdepth
} < <(
metaflac \
--show-sample-rate \
--show-channels \
--show-bps \
"$sourcepath/$filename"
)
}

54
lib/tags/getInfos::FLAC Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env bash
getInfosFLAC_version='FLAC-5'
tagreaders+=( "$getInfosFLAC_version" )
getInfos::FLAC() {
local \
infos \
tagreader="$getInfosFLAC_version"
infos=$(
metaflac \
--show-sample-rate \
--show-channels \
--show-bps \
--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=RELEASECOUNTRY \
--show-tag=REPLAYGAIN_ALBUM_GAIN \
--show-tag=REPLAYGAIN_TRACK_GAIN \
--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)
releasecountry=$(gettag releasecountry)
replaygain_alb=$(gettag replaygain_album_gain)
replaygain_trk=$(gettag replaygain_track_gain)
title=$(gettag title)
tracknum=$(gettag tracknumber)
tracktotal=$(gettag tracktotal)
year=$(gettag date)
if [ -n "$tracknum" -a -n "$tracktotal" ]
then
tracknum="$tracknum/$tracktotal"
fi
year=$(gettag DATE)
{
read rate
read channels
read depth
} <<<"$infos"
}

View File

@ -1,8 +1,11 @@
#!/bin/bash
getInfosOpus_version='Opus-2'
#!/usr/bin/env bash
getInfosOpus_version='Opus-4'
tagreaders+=( "$getInfosOpus_version" )
getInfos::Opus() {
tagreader="$getInfosOpus_version"
local \
infos \
infos=$(
opusinfo "$sourcepath/$filename" \
| sed 's/\t//'
@ -14,6 +17,9 @@ getInfos::Opus() {
disc=$(gettag discnumber)
genre=$(gettag genre)
performer=$(gettag performer)
releasecountry=$(gettag releasecountry)
replaygain_alb=$(gettag replaygain_album_gain)
replaygain_trk=$(gettag replaygain_track_gain)
title=$(gettag title)
tracknum=$(gettag tracknumber)
tracktotal=$(gettag tracktotal)
@ -24,7 +30,9 @@ getInfos::Opus() {
year=$(gettag date)
infos="${infos//: /=}"
rate=$(gettag 'original sample rate'|head -n1)
rate=${rate% Hz}
channels=$(gettag channels|head -n1)
bitrate=$(gettag 'average bitrate')
bitrate=${bitrate%% kbit*}
bitrate=${bitrate%%.*}
}

75
lib/tags/getInfos::ffmpeg Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
getInfosffmpeg_version='ffmpeg-9'
tagreaders+=( "$getInfosffmpeg_version" )
getInfos::ffmpeg() {
tagreader="$getInfosffmpeg_version"
local \
infos \
infos=$(
ffprobe -v error \
-show_entries " \
format_tags= \
album_artist, \
album, \
artist, \
composer, \
disc, \
genre, \
TOPE, \
releasecountry, \
'MusicBrainz Album Release Country',\
title, \
track, \
date, \
replaygain_track_gain, \
replaygain_album_gain \
:stream= \
bit_rate, \
channels, \
sample_rate, \
" \
-of default=noprint_wrappers=1 \
-i "$sourcepath/$filename" \
| egrep -v '=N/A$'
)
albumartist=$(gettag TAG:album_artist)
album=$(gettag TAG:album)
artist=$(gettag TAG:artist)
composer=$(gettag TAG:composer)
disc=$(gettag TAG:disc)
genre=$(gettag TAG:genre)
performer=$(gettag TAG:TOPE)
releasecountry=$(gettag TAG:releasecountry)
[[ -z "$releasecountry" ]] \
&& releasecountry=$(gettag "TAG:MusicBrainz Album Release Country")
replaygain_alb=$(gettag TAG:replaygain_album_gain)
replaygain_trk=$(gettag TAG:replaygain_track_gain)
title=$(gettag TAG:title)
tracknum=$(gettag TAG:track)
year=$(gettag TAG:date)
expr='^[0-9]*$'
if [ -n "$genre" ] && [[ $genre =~ $expr ]]
then
genre="${id3genres[$genre]}"
fi
channels=$(gettag channels)
rate=$(gettag 'sample_rate')
case $rate in
96) rate=96000;;
48) rate=48000;;
441) rate=44100;;
32) rate=32000;;
24) rate=24000;;
225) rate=22500;;
esac
bitrate=$(gettag 'bit_rate')
depth=$(gettag 'sample_fmt')
depth=${depth//[A-z]/}
if [[ $bitrate == N/A ]]
then
unset bitrate
else
bitrate=$((bitrate / 1000))
fi
}

View File

@ -1,10 +1,14 @@
#!/bin/bash
getInfosSoxi_version='soxi-1'
#!/usr/bin/env bash
getInfosSoxi_version='soxi-3'
tagreaders+=( "$getInfosSoxi_version" )
getInfos::soxi() {
tagreader="$getInfosSoxi_version"
local \
infos \
infos=$(
soxi "$sourcepath/$filename"
soxi "$sourcepath/$filename" \
| grep -v METADATA_BLOCK_PICTURE
)
albumartist=$(gettag albumartist)
album=$(gettag album)
@ -13,6 +17,9 @@ getInfos::soxi() {
disc=$(gettag discnumber)
genre=$(gettag genre)
performer=$(gettag performer)
releasecountry=$(gettag releasecountry)
replaygain_alb=$(gettag replaygain_album_gain)
replaygain_trk=$(gettag replaygain_track_gain)
title=$(gettag title)
tracknum=$(gettag tracknumber)
tracktotal=$(gettag tracktotal)
@ -27,6 +34,6 @@ getInfos::soxi() {
bitrate=$(gettag 'bit rate')
bitrate=${bitrate%k}
bitrate=${bitrate%%.*}
bitdepth=$(gettag precision)
bitdepth=${bitdepth%-bit}
depth=$(gettag precision)
depth=${depth%-bit}
}

View File

@ -11,11 +11,15 @@ getTags() {
type=Opus
(( disableopusinfo )) && unset type
;;
'audio/ogg opus')
type=Opus
(( disableopusinfo )) && unset type
;;
application/ogg*)
type=soxi
(( disablesoxi )) && unset type
;;
audio/ogg)
audio/ogg*)
type=soxi
(( disablesoxi )) && unset type
;;

View File

@ -1,6 +0,0 @@
#!/bin/bash
tryAPE() {
grep -q 'APETAGEX' \
"$sourcepath/$filename" \
&& type=APE
}

View File

@ -1,5 +1,7 @@
#!/bin/bash
#!/usr/bin/env bash
updateTags() {
local reader \
for reader in "${tagreaders[@]}"
do
tagreaderclause+="${tagreaderclause:+ AND }NOT tags.tagreader = \"$reader\""
@ -18,6 +20,9 @@ updateTags() {
tags.disc,
tags.genre,
tags.performer,
tags.releasecountry,
tags.replaygain_alb,
tags.replaygain_trk,
tags.title,
tags.track,
tags.year,
@ -81,6 +86,12 @@ echo '
rest=${rest#*::AtOM:SQL:Sep::}
oldperformer=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
oldreleasecountry=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
oldreplaygain_alb=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
oldreplaygain_trk=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
oldtitle=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
oldtrack=${rest%%::AtOM:SQL:Sep::*}
@ -94,7 +105,7 @@ echo '
oldbitrate=${rest%%::AtOM:SQL:Sep::*}
((++count))
(( cron )) || echo -en "\rTags: $((count*100/filecount))%"
if (( count % 1000 == 0 ))
if (( count % 100 == 0 ))
then
echo 'COMMIT;BEGIN TRANSACTION;' >&3
(( debug )) \
@ -106,10 +117,13 @@ echo '
[[ $oldalbumartist != "$albumartist" ]]&&uaa=1
[[ $oldartist != "$artist" ]]&& uar=1
[[ $oldcomposer != "$composer" ]]&& uco=1
[[ $olddepth != "$bitdepth" ]]&& ude=1
[[ $olddepth != "$depth" ]]&& ude=1
[[ $olddisc != "$disc" ]]&& udi=1
[[ $oldgenre != "$genre" ]]&& uge=1
[[ $oldperformer != "$performer" ]]&& upe=1
[[ $oldreleasecountry != "$releasecountry" ]]&& urc=1
[[ $oldreplaygain_alb != "$replaygain_alb" ]]&& urpa=1
[[ $oldreplaygain_trk != "$replaygain_trk" ]]&& urpt=1
[[ $oldtitle != "$title" ]]&& uti=1
[[ $oldtrack != "$tracknum" ]]&& utr=1
[[ $oldyear != "$year" ]]&& uye=1
@ -121,10 +135,13 @@ echo '
${uaa:+albumartist "${albumartist:+::AtOM:FT::}${albumartist:-NULL}"}\
${uar:+artist "${artist:+::AtOM:FT::}${artist:-NULL}"}\
${uco:+composer "${composer:+::AtOM:FT::}${composer:-NULL}"}\
${ude:+depth "${bitdepth:-NULL}"} \
${ude:+depth "${depth:-NULL}"} \
${udi:+disc "${disc:-NULL}"} \
${uge:+genre "${genre:-NULL}"} \
${upe:+performer "${performer:+::AtOM:FT::}${performer:-NULL}"}\
${urc:+releasecountry "${releasecountry:+::AtOM:FT::}${releasecountry:-NULL}"}\
${urpa:+replaygain_alb "${replaygain_alb:-NULL}"}\
${urpt:+replaygain_trk "${replaygain_trk:-NULL}"}\
${uti:+title "${title:+::AtOM:FT::}${title:-NULL}"}\
${utr:+track "${track:+::AtOM:FT::}${tracknum:-NULL}"}\
${uye:+year "${year:-NULL}"} \
@ -133,35 +150,58 @@ echo '
${uch:+channels "${channels:-NULL}"} \
${ubi:+bitrate "${bitrate:-NULL}"} \
tagreader "$tagreader" \
>/dev/null <<<"source_file = $sourcefileid"
unset genre \
albumartist \
year \
album \
disc \
artist \
tracknum \
title \
composer \
performer \
rate \
bitdepth \
bitrate \
channels \
ual \
uaa \
uar \
uco \
ude \
udi \
uge \
upe \
uti \
utr \
uye \
ura \
uch \
ubi
>/dev/null <<<"source_file = $sourcefileid"
unset genre \
albumartist \
year \
album \
disc \
artist \
tracknum \
title \
composer \
performer \
releasecountry \
replaygain_alb \
replaygain_trk \
rate \
depth \
bitrate \
channels \
oldgenre \
oldalbumartist \
oldyear \
oldalbum \
olddisc \
oldartist \
oldtracknum \
oldtitle \
oldcomposer \
oldperformer \
oldreleasecountry \
oldreplaygain_alb \
oldreplaygain_trk \
oldrate \
olddepth \
oldbitrate \
oldchannels \
ual \
uaa \
uar \
uco \
ude \
udi \
uge \
upe \
urc \
urpa \
urpt \
uti \
utr \
uye \
ura \
uch \
ubi
fi
done
echo 'COMMIT;' >&3

View File

@ -4,7 +4,6 @@ gettaskinfos() {
SELECT
id,
source_file,
required,
cleanup,
fileid,
filename
@ -16,8 +15,6 @@ gettaskinfos() {
rest="${line#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
sourcefileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
required=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
cleanup=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
destfileid=${rest%%::AtOM:SQL:Sep::*}

166
lib/tools/sanityChecks Normal file
View File

@ -0,0 +1,166 @@
#!/usr/bin/env bash
sanityCheck() {
if [ ! -d "$tempdir" ] && ! mkdir -p "$tempdir"
then
echo "[FATAL] Could not create temp directory $tempdir" >&2
(( sanityfail++ ))
fi
if [ ! -f "$database" ] && [ ! -d "${database%/*}" ] \
&& ! mkdir -p "${database%/*}"
then
echo "[FATAL] Directory holding database file does not exist"\
"and could not be created" >&2
(( sanityfail++ ))
fi
if [ ! -d "$sourcepath" ]
then
echo "[FATAL] Source path $sourcepath does not exist or is"\
"not a directory" >&2
(( sanityfail++ ))
fi
if ! which sed >/dev/null
then
echo "[FATAL] Required tool sed is not installed or not in PATH
I never thought this would actually hit someone..." >&2
(( sanityfail++ ))
fi
if ! which sox >/dev/null
then
echo "[FATAL] Required tool sox is not installed or not in"\
"PATH" >&2
(( sanityfail++ ))
fi
if ! which ogginfo >/dev/null
then
echo "[WARNING] Tool ogginfo (from vorbis-tools) is not"\
"installed or not in PATH
WebM metadata disabled" >&2
disableogginfo=1
(( sanitywarn++ ))
fi
if ! which soxi >/dev/null
then
echo "[WARNING] Tool soxi (from sox) is not" \
"installed or not in PATH
Vorbis metadata disabled" >&2
disablesoxi=1
(( sanitywarn++ ))
fi
if (( oggencneeded )) && ! which oggenc >/dev/null
then
echo "[WARNING] Tool oggenc (from vorbis-tools) is not" \
"installed or not in PATH
Vorbis targets disabled" >&2
disableoggenc=1
(( sanitywarn++ ))
fi
if ! which opusinfo >/dev/null
then
echo "[WARNING] Tool opusinfo (from opus-tools) is not" \
"installed or not in PATH
Opus metadata disabled" >&2
disableopusinfo=1
(( sanitywarn++ ))
fi
if (( opusencneeded )) && ! which opusenc >/dev/null
then
echo "[WARNING] Tool opusenc (from opus-tools) is not" \
"installed or not in PATH
Opus targets disabled" >&2
disableopusenc=1
(( sanitywarn++ ))
fi
if ! which opusdec >/dev/null
then
echo "[WARNING] Tool opusdec (from opus-tools) is not" \
"installed or not in PATH
Opus support disabled" >&2
disableopusdec=1
(( sanitywarn++ ))
fi
if (( lameneeded )) && ! which lame >/dev/null
then
echo "[WARNING] Tool lame is not installed or not in PATH
MP3 targets disabled" >&2
disablelame=1
(( sanitywarn++ ))
fi
if ! which metaflac >/dev/null
then
echo "[WARNING] Tool metaflac (from FLAC) is not installed"\
"or not in PATH
FLAC metadata disabled" >&2
disableflac=1
(( sanitywarn++ ))
fi
if ! which mpcdec >/dev/null
then
echo "[WARNING] Tool mpcdec (from Musepack) is not" \
"installed or not in PATH
Musepack support disabled" >&2
disablempcdec=1
(( sanitywarn++ ))
fi
if ! which mkvextract >/dev/null
then
echo "[WARNING] Tool mkvextract (from MKVToolNix) is not"\
"installed or not in PATH
WebM metadata disabled
WebM support disabled" >&2
disablemkvextract=1
(( sanitywarn++ ))
fi
if ! which ffprobe >/dev/null
then
echo "[WARNING] Tool ffprobe (from FFmpeg) is not installed"\
"or not in PATH
Video metadata disabled
MPEG metadata disabled
MusePack metadata disabled
Unknown format metadata disabled" >&2
disableffprobe=1
(( sanitywarn++ ))
fi
if ! which ffmpeg >/dev/null
then
echo "[WARNING] Tool ffmpeg is not installed or not in PATH
Video support disabled" >&2
disablevideo=1
(( sanitywarn++ ))
fi
if (( textunidecodeneeded )) && ! perl -MText::Unidecode -e 'exit;' 2>/dev/null
then
echo "[WARNING] Perl module Text::Unidecode is not available
Renaming to ASCII-only disabled" >&2
unset destinationascii
destinationascii=0
textunidecodeneeded=0
(( sanitywarn++ ))
fi
if (( sanityfail ))
then
echo "
Sanity checks raised ${sanitywarn:-0} warnings, $sanityfail failures. Dying now." >&2
exit $ESANITY
elif (( sanitywarn ))
then
echo "
Sanity checks raised $sanitywarn warnings... Hit Control-C to abort." >&2
if ! (( cron ))
then
timeout=$(( sanitywarn * 10 ))
echo -n "Starting in $(printf %3i $timeout)" \
$'seconds...\b\b\b\b\b\b\b\b\b\b\b' >&2
while (( timeout ))
do
echo -n $'\b\b\b'"$(printf %3i $timeout)" >&2
sleep 1
(( timeout-- ))
done
echo -en "\r\033[K"
fi
fi
}
# vim:set ts=8 sw=8:

View File

@ -1,5 +1,9 @@
#!/bin/bash
checkworkers() {
local \
taskid \
parent_required \
parent_task
for key in ${!workers[@]}
do
if ! kill -0 ${workers[key]} 2>/dev/null
@ -14,6 +18,19 @@ checkworkers() {
failedtasks+=($taskid)
(( ++failed ))
fi
parent_task=$(
Select tasks requires \
<<<"id = $taskid"
)
if (( parent_task ))
then
parent_required=$(
Select tasks required_by \
<<<"id = $parent_task"
)
Update tasks required_by $((--parent_required)) \
<<<"id = $parent_task"
fi
unset workertasks[key]
fi
done

View File

@ -1,26 +1,23 @@
#!/bin/bash
cleaner() {
local \
key \
faildepends \
taskid \
count
for key in ${!failedtasks[@]}
do
taskid=${failedtasks[key]}
gettaskinfos $taskid
faildepends=$(
Select tasks 'COUNT(*)' <<-EOWhere
requires = $taskid
Select tasks required_by <<-EOWhere
id = $taskid
EOWhere
)
(( failed+=faildepends ))
(( ran+=faildepends ))
Update tasks status 2 <<<"id = $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
rm -f "$cleanup"
unset failedtasks[key]
done
for key in ${!finishedtasks[@]}
@ -47,29 +44,28 @@ cleaner() {
" FROM tasks" \
" WHERE id=$taskid" \
" )," \
" fat32compat=(" \
" fat32compat=(" \
" SELECT fat32compat" \
" FROM tasks" \
" WHERE id=$taskid" \
" )," \
" ascii=(" \
" SELECT ascii" \
" ascii=(" \
" SELECT ascii" \
" 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
count=$(Select tasks required_by <<<"id = $taskid")
if (( count == 0 ))
then
rm -f "$cleanup"
[[ -n "$cleanup" ]] && rm -f "$tempdir/$cleanup"
Delete tasks <<<"id = $taskid"
unset finishedtasks[key]
else
Update tasks status 4 \
<<<"id = $taskid"
fi
Delete tasks <<<"id = $taskid"
unset finishedtasks[key]
done
}

View File

@ -1,5 +1,145 @@
#!/bin/bash
#!/usr/bin/env bash
createworker() {
worker $1 &
workers[$1]=$!
}
(( ++active ))
read -u4 line
taskid=${line%%::AtOM:SQL:Sep::*}
rest="${line#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
sourcefileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cleanup=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
destfileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
destfilename=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
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"
worker $workerid &
workers[$workerid]=$!
}

View File

@ -8,261 +8,233 @@ master() {
SELECT COUNT(*)
FROM tasks
WHERE status = 0;
'>&3
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,
cmd_arg30,
cmd_arg31,
cmd_arg32,
cmd_arg33,
cmd_arg34,
cmd_arg35,
cmd_arg36,
cmd_arg37,
cmd_arg38,
cmd_arg39,
cmd_arg40,
cmd_arg41,
cmd_arg42,
cmd_arg43,
cmd_arg44,
cmd_arg45,
cmd_arg46,
cmd_arg47,
cmd_arg48,
cmd_arg49,
cmd_arg50,
cmd_arg51,
cmd_arg52,
cmd_arg53,
cmd_arg54,
cmd_arg55,
cmd_arg56,
cmd_arg57,
cmd_arg58,
cmd_arg59,
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
return 0
elif (( active == 0 && ready == 0 ))
then
dumpfile=tasks-$(date +%Y%m%d%H%M%S).csv
cat <<-EOF
$remaining TASKS LEFT, NONE READY!
Something went wrong, dumping tasks table to $dumpfile
EOF
cat >&3 <<-EOSQL
.mode csv
.headers on
.output $dumpfile
SELECT * from tasks;
.mode list
.headers off
.output stdout
EOSQL
closeDatabase
echo "Waiting for children to come back home..."
wait
echo $'\nGood luck!'
exit 1
elif (( ready == 0 ))
then
sleep 0.1
else
(( ++active ))
read -u4 line
taskid=${line%%::AtOM:SQL:Sep::*}
rest="${line#*::AtOM:SQL:Sep::}::AtOM:SQL:Sep::"
sourcefileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
required=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cmd_arg+=("${rest%%::AtOM:SQL:Sep::*}")
rest=${rest#*::AtOM:SQL:Sep::}
cleanup=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
destfileid=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
destfilename=${rest%%::AtOM:SQL:Sep::*}
rest=${rest#*::AtOM:SQL:Sep::}
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
until (( active == concurrency || remaining == 0 ))
do
echo '
SELECT COUNT(*)
FROM tasks
WHERE status = 0
AND requires IN (
SELECT id
FROM tasks
WHERE status = 4
);
SELECT
id,
source_file,
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,
cmd_arg30,
cmd_arg31,
cmd_arg32,
cmd_arg33,
cmd_arg34,
cmd_arg35,
cmd_arg36,
cmd_arg37,
cmd_arg38,
cmd_arg39,
cmd_arg40,
cmd_arg41,
cmd_arg42,
cmd_arg43,
cmd_arg44,
cmd_arg45,
cmd_arg46,
cmd_arg47,
cmd_arg48,
cmd_arg49,
cmd_arg50,
cmd_arg51,
cmd_arg52,
cmd_arg53,
cmd_arg54,
cmd_arg55,
cmd_arg56,
cmd_arg57,
cmd_arg58,
cmd_arg59,
cleanup,
fileid,
filename
FROM tasks
WHERE status = 0
AND requires IN (
SELECT id
FROM tasks
WHERE status = 4
ORDER BY source_file
/* LIMIT 1 */
)
ORDER BY source_file
LIMIT 1;
'>&3
read -u4 ready
if (( ready > 0 ))
then
createworker
continue
fi
echo '
SELECT COUNT(*)
FROM tasks
WHERE status = 0
AND requires is NULL;
SELECT
id,
source_file,
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,
cmd_arg30,
cmd_arg31,
cmd_arg32,
cmd_arg33,
cmd_arg34,
cmd_arg35,
cmd_arg36,
cmd_arg37,
cmd_arg38,
cmd_arg39,
cmd_arg40,
cmd_arg41,
cmd_arg42,
cmd_arg43,
cmd_arg44,
cmd_arg45,
cmd_arg46,
cmd_arg47,
cmd_arg48,
cmd_arg49,
cmd_arg50,
cmd_arg51,
cmd_arg52,
cmd_arg53,
cmd_arg54,
cmd_arg55,
cmd_arg56,
cmd_arg57,
cmd_arg58,
cmd_arg59,
cleanup,
fileid,
filename
FROM tasks
WHERE status = 0
AND requires is NULL
ORDER BY source_file
LIMIT 1;
' >&3
read -u4 ready
if (( active == 0 && ready == 0 ))
then
dumpfile="$tempdir/tasks-$(date -Iseconds).csv"
cat <<-EOF
$remaining TASKS LEFT, NONE READY!
Something went wrong, dumping tasks table to $dumpfile
EOF
cat >&3 <<-EOSQL
.mode csv
.headers on
.output $dumpfile
SELECT * from tasks;
.mode list
.headers off
.output stdout
COMMIT;
EOSQL
closeDatabase
echo "Waiting for children to come back home..."
wait
echo $'\nGood luck!'
exit 1
elif (( ready == 0 ))
then
sleep 0.1
break
else
createworker
fi
echo '
SELECT COUNT(*)
FROM tasks
WHERE status = 0;
'>&3
read -u4 remaining
done
fi
}

35
lib/xdg/migrate Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# https://specifications.freedesktop.org/basedir-spec/latest/
xdgUpdateData() {
local -r programname=AtOM
local -r my_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/$programname"
local -r new_db_path="$my_data_home/atom.db"
[[ -d "$my_data_home" ]] || mkdir -p "$my_data_home"
mv "$1" "$new_db_path"
echo "$new_db_path"
}
xdgUpdateRuntime() {
local -r programname=AtOM \
my_runtime_dir="${XDG_RUNTIME_DIR:-/tmp}" \
echo "$my_runtime_dir/$programname"
}
xdgMigrate() {
local -r programname=AtOM
local -r my_config_home="${XDG_CONFIG_HOME:-$HOME/.config}/$programname"
local new_database
cffile="$HOME/.atom/atom.cfg"
getConfig
new_database=$(xdgUpdateData "$database")
database="$new_database"
tempdir=$(xdgUpdateRuntime)
cffile="$my_config_home/atom.cfg"
[[ -d "$my_config_home" ]] || mkdir -p "$my_config_home"
writeConfig >"$cffile"
}

View File

@ -58,6 +58,9 @@ CREATE TABLE IF NOT EXISTS tags (
title TEXT,
composer TEXT,
performer TEXT,
releasecountry TEXT,
replaygain_alb TEXT,
replaygain_trk TEXT,
depth INTEGER,
rate INTEGER,
channels INTEGER,
@ -129,10 +132,13 @@ CREATE TRIGGER IF NOT EXISTS force_destination_update_on_tag_update
title,
composer,
performer,
releasecountry,
replaygain_alb,
replaygain_trk,
rate,
channels,
bitrate,
bitdepth
depth
ON tags
BEGIN
UPDATE destination_files SET last_change=0

View File

@ -23,16 +23,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -42,6 +41,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'urnD' opt
do
case $opt in
@ -54,6 +62,7 @@ done
getConfig
sanityCheck
openDatabase
(( update )) && getFiles

View File

@ -23,16 +23,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -42,6 +41,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'D' opt
do
case $opt in
@ -50,7 +58,7 @@ do
done
getConfig
sanityCheck
openDatabase
sourcepath=''

View File

@ -23,16 +23,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -42,6 +41,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'Dr' opt
do
case $opt in
@ -51,7 +59,7 @@ do
done
getConfig
sanityCheck
openDatabase
echo 'SELECT id,filename FROM destination_files WHERE filename IS NOT NULL;' >&3

View File

@ -23,16 +23,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -42,6 +41,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'rD' opt
do
case $opt in
@ -51,7 +59,7 @@ do
done
getConfig
sanityCheck
openDatabase
checkwanted() {

View File

@ -25,28 +25,35 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
source ./share/id3genres
source "$SHAREDIR"/id3genres
for function in "$LIBDIR"/*/*
do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
args="$@"
while [ -n "$1" ]
do
@ -66,6 +73,7 @@ do
'-M') show+=(types) ;;
'-N') show+=(tracktotals) ;;
'-p') show+=(performers) ;;
'-r') show+=(releasecountries) ;;
'-s') show+=(rates) ;;
'-S') show+=(size)
length[count]=5 ;;
@ -114,6 +122,7 @@ done
-d Disc
-g Genre
-p Performer
-r Release Country
-N Track total
-t Title
-y Year
@ -130,7 +139,7 @@ done
}
getConfig
sanityCheck
openDatabase
columns="${show[@]//*/-}"
@ -273,17 +282,17 @@ fi
cat <<-EOBrag
# Generated by AtOM's createindex toy.
# https://gitorious.org/atom
# (C) 2012-2013 Vincent Riquer (GPL-3)
# https://framagit.org/atom/AtOM/
# (C) 2012-2025 Vincent Riquer (GPL-3)
#
# $0 $args
#
# Last database update: $(date -d @$lastupdate +'%x %X')
# Last database update: $(printf "%(%x %X)T" "$lastupdate")
EOBrag
printDate() {
date -d"@$1" +"${timeformat:-%x %X}"
printf "%("${timeformat:-%x %X}")T" "$1"
}
for index in ${!show[@]}
@ -305,6 +314,7 @@ do
oldtimestamp) info='Last modified' ;;
types) info='Format' ;;
performers) info='Performer' ;;
releasecountries) info='Country' ;;
rates) info='Sample rate' ;;
titles) info='Title' ;;
tracktotals) info='Track total' ;;
@ -338,6 +348,7 @@ SELECT
tags.disc,
tags.genre,
tags.performer,
tags.releasecountry,
tags.title,
tags.track,
tags.year,
@ -396,6 +407,8 @@ do
rest="${rest#*::AtOM:SQL:Sep::}"
performer="${rest%%::AtOM:SQL:Sep::*}"
rest="${rest#*::AtOM:SQL:Sep::}"
releasecountry="${rest%%::AtOM:SQL:Sep::*}"
rest="${rest#*::AtOM:SQL:Sep::}"
title="${rest%%::AtOM:SQL:Sep::*}"
rest="${rest#*::AtOM:SQL:Sep::}"
track="${rest%%::AtOM:SQL:Sep::*}"
@ -523,6 +536,13 @@ do
[ -n "$performer" ] \
&& performers+="${performers+,}$performer"
fi
if ! [[ $releasecountries =~ $expr1"$releasecountry"$expr2 ]]
then
[ -z "$releasecountries" ] \
&& unset releasecountries
[ -n "$releasecountry" ] \
&& releasecountries+="${releasecountries+,}$releasecountry"
fi
if ! [[ $titles =~ $expr1"$title"$expr2 ]]
then
[ -z "$titles" ] \
@ -549,10 +569,10 @@ do
then
printline
fi
unset bitrates
unset bitrates depths rates
channelss="$channels"
rates="$rate"
depths="$depth"
(( rate )) && rates="$rate"
(( depth )) && depths="$depth"
types="$type"
albumartists="$albumartist"
albums="$album"
@ -561,6 +581,7 @@ do
discs="$disc"
genres="$genre"
performers="$performer"
releasecountries="$releasecountry"
titles="$title"
tracktotals="$tracktotal"
years="$year"

View File

@ -23,16 +23,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -42,6 +41,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'fm:o:p:*:uD' opt
do
case $opt in
@ -67,7 +75,7 @@ do
done
getConfig
sanityCheck
openDatabase
if (( update ))

View File

@ -24,16 +24,15 @@ declare -A \
}
declare -r \
DOCDIR=./doc \
LIBDIR=./lib \
SHAREDIR=./share
DOCDIR=%DOCDIR% \
LIBDIR=%LIBDIR% \
SHAREDIR=%SHAREDIR%
declare -r \
exampleconf=$DOCDIR/example.cfg \
schema=$SHAREDIR/schema.sql \
\
oldIFS="$IFS"
cffile="$HOME/.atom/atom.cfg"
LC_ALL=C
shopt -s extglob
@ -43,6 +42,15 @@ do
source "$function"
done
if ! [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg" ]] \
&& [[ -f "$HOME/.atom/atom.cfg" ]]
then
echo "Configuration found in legacy location $HOME/.atom/atom.cfg."\
"Migrating configuration and data to XDG standard"
xdgMigrate
fi
cffile="${XDG_CONFIG_HOME:-$HOME/.config}/AtOM/atom.cfg"
while getopts 'AlacdgptnNyuD' opt
do
case $opt in
@ -86,7 +94,7 @@ done
}
getConfig
sanityCheck
openDatabase
if (( update ))

503
transogg
View File

@ -1,503 +0,0 @@
#!/bin/bash
cat <<-EOF
This script is buggy and may destroy data
DON'T USE IT!
AtOM will be a clean and safe rewrite of this dirty hack.
I repeat: DON'T use this script.
EOF
exit
source /etc/transogg.conf
[ ! -d "$TEMPDIR" ] && mkdir -p "$TEMPDIR"
# log errors
exec 2> $TEMPDIR/debug-$$.log
set -E
MyPID=$$
tty=$(who -m|awk '{print $2}')
cleanup() {
pkill -t $tty find||true
unset files
export killed=1
}
export -f cleanup
usage() {
cat <<-EOF
$0 [-D] [-n <instances>] [-F] [-s <source directory>] [-t <target directory>]
-n <instances>: run <instances> simultaneous processes (overrides config
file)
-l <max load>: Don't start transcoding if the load average reaches <max
load>
-T <time>: time before raising instance number again after reaching max
load
-F: ignore any existing timestamp file (useful if you change target
quality)
-D: increase debug level (produces potentially huge log files in
$TEMPDIR)
-c <conf-file>: Alternate configuration file
EOF
}
trap cleanup 1 2 15 EXIT
declare -i DEBUG=0
while getopts 'l:FhiDs:t:T:c:' opt
do
case $opt in
s) ORIGDIR=$OPTARG ;;
t) DESTDIR=$OPTARG ;;
l) TARGETLOAD=$OPTARG
;;
F) rm -f "$TIMESTAMP"
;;
h) usage ; exit 0 ;;
T) LOADTIME=$OPTARG ;;
D) DEBUG+=1; set -x ;;
c) source "$OPTARG" ;;
*) exit 127
;;
esac
done
(( DEBUG >= 2 )) && set -v
COLUMNS=$(tput cols)
LINES=$(tput lines)
trap 'COLUMNS=$(tput cols) LINES=$(tput lines)' WINCH
declare -a children
declare -i filenum=0
declare -i lastid=-1
FIFO=/tmp/transogg-$UID-$MyPID-status
IDFIFO=/tmp/transogg-$UID-$MyPID-finished
shopt -s extglob
declare -i lastINSTchange=0
((instances = TARGETLOAD / 2))
[ -d "$TEMPDIR" ] \
|| mkdir -p "$TEMPDIR" \
|| exit 1
[ -d "$DESTDIR" ] \
|| mkdir -p "$DESTDIR" \
|| exit 1
[ -f "$TIMESTAMP" ] \
|| touch -t 197001010100 "$TIMESTAMP"
case $FORMAT in
ogg)
function encode {
nice -n $NICENESS ionice -c3 \
oggenc \
-Q \
-a "${artist:-?}" \
-l "${album:-?}" \
-N "${tracknum}${tracktotal/#//}" \
-t "${title:-$(basename "$1")}" \
-q $QUALITY \
-d "$year" \
-G "$genre" \
-c DISCNUMBER="$disc" \
-o "$2" \
$tmpfile \
2> /dev/null
}
;;
mp3)
function encode {
nice -n $NICENESS ionice -c3 \
lame \
--quiet \
--ta "${artist:-?}" \
--tl "${album:-?}" \
--tn "${tracknum}${tracktotal/#//}" \
--tt "${title:-$(basename "$1")}" \
-v \
--abr $BITRATE \
--ty "$year" \
--tg "$genre" \
$tmpfile \
"$2" \
2> /dev/null
}
;;
*)
exit 127
;;
esac
gettag() {
echo -e "$1" \
| egrep -i '^'${2}= \
| cut -d = -f 2
}
mpc_info() {
infos=$(ionice -c3 \
strings "$1" \
| egrep -A1 'artist|album|title|track|year|genre')
title=$(echo -e "$infos"|sed -n /title/I {n;p})
artist=$(echo -e "$infos"|sed -n /artist/I {n;p})
track=$(echo -e "$infos"|sed -n /track/I {n;p})
album=$(echo -e "$infos"|sed -n /album/I {n;p})
year=$(echo -e "$infos"|sed -n /year/I {n;p})
genre=$(echo -e "$infos"|sed -n /genre/I {n;p})
unset infos
}
flac_info() {
infos=$(ionice -c3 \
metaflac \
--show-tag=TITLE \
--show-tag=ARTIST \
--show-tag=ALBUM \
--show-tag=TRACKNUMBER \
--show-tag=TRACKTOTAL \
--show-tag=DATE \
--show-tag=DISCNUMBER \
--show-tag=GENRE \
"$1")
artist=$(gettag "$infos" artist)
album=$(gettag "$infos" album)
title=$(gettag "$infos" title)
tracknum=$(gettag "$infos" tracknumber)
tracktotal=$(gettag "$infos" tracktotal)
year=$(gettag "$infos" date)
disc=$(gettag "$infos" discnumber)
genre=$(gettag "$infos" genre)
unset infos
}
function vorbis_info {
infos=$(ionice -c3 ogginfo "$1" | sed 's/^\t//')
artist=$(gettag "$infos" artist)
album=$(gettag "$infos" album)
title=$(gettag "$infos" title)
tracknum=$(gettag "$infos" tracknumber)
tracktotal=$(gettag "$infos" tracktotal)
year=$(gettag "$infos" date)
disc=$(gettag "$infos" discnumber)
genre=$(gettag "$infos" genre)
unset infos
}
function mp3_info {
infos=$(ionice -c3 mp3info -p 'artist=%a
album=%l
title=%t
tracknum=%n
year=%y
genre=%g' "$1" | iconv -f $ID3CHARSET -t utf-8//TRANSLIT)
artist=$(gettag "$infos" artist|iconv -t utf-8)
album=$(gettag "$infos" album|iconv -t utf-8)
title=$(gettag "$infos" title|iconv -t utf-8)
tracknum=$(gettag "$infos" tracknum|iconv -t utf-8)
year=$(gettag "$infos" year)
genre=$(gettag "$infos" genre)
unset infos
}
function get_info {
case "$(file "$1")" in
*Musepack*)
mpc_info "$1"
;;
*MPEG*)
mp3_info "$1"
;;
*MP3*)
mp3_info "$1"
;;
*Vorbis*)
vorbis_info "$1"
;;
*FLAC*)
flac_info "$1"
;;
*)
:
;;
esac
}
function theRealSox {
echo "$1: $2 → $3" >&2
get_info "$2" 2>/dev/null
tmpfile="$TEMPDIR"/transogg-$UID-$(date +%Y%m%d%H%M%S%N).wav
nice -n $NICENESS ionice -c3 \
sox \
--norm \
--single-threaded "$2" \
-r ${RATE:=44100} \
-c ${CHANNELS:=2} \
--temp $TEMPDIR \
$tmpfile \
> /dev/null || true
encode "$2" "$3" || true
rm -f $tmpfile
unset artist album tracknum tracktotal title year genre tmpfile
until echo $1 >> $IDFIFO
do
sleep 1
done
}
function CleanIdFile {
while read curid
do
if (( lastid + 1 == $curid ))
then
if [ -n "${files[$curid]}" ]
then
touch -r "$ORIGDIR/${files[$curid]}" "$TIMESTAMP"
fi
sed -n -i "/^$curid$/d;p" $IDFIFO
lastid=$curid
fi
unset children[$curid]
echo -n '' > $FIFO
for processInd in ${!children[@]}
do
echo ${children[$processInd]} >> $FIFO
done
echo $filenum $instances $TARGETLOAD $message
done < <(sort -n $IDFIFO)
}
function MySox {
load=$(sed 's/^\([0-9]\+\)[. ].*/\1/' /proc/loadavg)
if (( load > TARGETLOAD && instances > 1 && $(date +%s) - lastINSTchange >= LOADTIME))
then
((instances -= 1))
if ((instances < 1))
then
instances=1
fi
lastINSTchange=$(date +%s)
echo $filenum $instances $TARGETLOAD $message
elif (( load < TARGETLOAD && $(date +%s) - lastINSTchange >= LOADTIME))
then
((instances += 1))
lastINSTchange=$(date +%s)
echo $filenum $instances $TARGETLOAD $message
fi
CleanIdFile
until [ ${#children[@]} -lt $instances ]
do
load=$(sed 's/^\([0-9]\+\)[. ].*/\1/' /proc/loadavg)
if (( load > TARGETLOAD && instances > 1 && $(date +%s) - lastINSTchange >= LOADTIME))
then
((instances -= 1))
if ((instances < 1))
then
instances=1
fi
lastINSTchange=$(date +%s)
echo $filenum $instances $TARGETLOAD $message
elif (( load < TARGETLOAD && $(date +%s) - lastINSTchange >= LOADTIME))
then
((instances += 1))
lastINSTchange=$(date +%s)
echo $filenum $instances $TARGETLOAD $message
fi
CleanIdFile
read -t 0.5 -n 1 load_change||true
case $load_change in
+) ((TARGETLOAD += 1))
echo $filenum $instances $TARGETLOAD $message
;;
-) ((TARGETLOAD -= 1))
echo $filenum $instances $TARGETLOAD $message
;;
q) cleanup
message=Exiting...
echo $filenum $instances $TARGETLOAD $message
;;
esac
if ((instances < 1))
then
instances=1
echo $filenum $instances $TARGETLOAD $message
fi
unset message
done
theRealSox $filenum "$1" "$2" 2>>$TEMPDIR/debug-$MyPID-theRealSox &
children[$filenum]="${1#$ORIGDIR/}"
}
function FileDisappeared {
dialog --yes-label "Continue" \
--no-label "Rescan" \
--yesno "File $1 disappeared!" \
$LINES $COLUMNS
}
function FindFiles {
cd "$ORIGDIR" || {
echo "Could not cd into $ORIGDIR"
exit 1
}
cd -
readarray -t files < <(find "$ORIGDIR"/ \
-newer "$TIMESTAMP" \
-type f \
-printf "%TY%Tm%Td%TH%TM.%TS %P\n" \
| grep -v .timestamp \
| tee >(dialog --output-fd 1 --progressbox "Looking for new files..." \
$LINES $COLUMNS) \
| sort \
| sed 's/^[^ ]* //' )
}
function ShowProgress {
while read filenum concurrency targetload message
do
stamp=$(ls -go --time-style="+%d/%m/%Y %H:%M:%S" $TIMESTAMP | \
awk '{print $4 " " $5}')
if [ "$filenum" -gt 0 -a $nfiles -gt 0 ]
then
percent=$((${filenum:-${#files[@]}}*100/$nfiles))
else
percent=0
fi
echo XXX
echo $percent
echo "Position: ${filenum:-?} / $nfiles ($stamp) $message"
echo "$concurrency processes max. Target load: $targetload."
echo -e "(+/- to change, q to quit)\n"
sed 's/$/\n/' $FIFO
echo XXX
done \
| dialog \
--title "Transcoding files from $ORIGDIR to $DESTDIR..." \
--no-shadow \
--gauge "Transcoding files from $ORIGDIR to $DESTDIR..." \
$LINES $COLUMNS
}
function ProcessFiles {
lastINSTchange=$(date +%s)
for filenum in ${!files[@]}
do
filename=${files[$filenum]}
if [ -z "$filename" ]
then
break
fi
if [ ! -e "$ORIGDIR/$filename" ]
then
restart=1
return 0
fi
destfile="$DESTDIR/${filename%.*}.$FORMAT"
[ -d "${destfile%/?*}" ] || mkdir -p "${destfile%/?*}"
MySox "$ORIGDIR/$filename" "$destfile"
echo -n '' > $FIFO
for processInd in ${!children[@]}
do
echo ${children[$processInd]} >> $FIFO
done
echo $filenum $instances $TARGETLOAD $message
sleep 0.01
done
until [ ${#children[@]} -eq 0 ]
do
echo ${#files[@]} $instances $TARGETLOAD Finishing...
sleep 1
CleanIdFile
done
echo ${#files[@]} $instances $TARGETLOAD Done\!
}
function Work {
echo "Looking for new files..."
FindFiles 2>$TEMPDIR/debug-$MyPID-FindFiles
nfiles=${#files[@]}
ProcessFiles 2>$TEMPDIR/debug-$MyPID-ProcessFiles \
> >(ShowProgress 2>$TEMPDIR/debug-$MyPID-ShowProgress)
sleep 1
clear
if [ -n "$killed" ]
then
rm $FIFO $IDFIFO
exit 1
fi
while [ -n "$restart" ]
do
unset restart
if FileDisappeared
then
for indice in {0..$lastid}
do
unset files[$indice]
done
ProcessFiles 2>>$TEMPDIR/debug-$MyPID-ProcessFiles \
> >(ShowProgress 2>>$TEMPDIR/debug-$MyPID-ShowProgress)
else
Work
fi
done
}
Work
rm $FIFO $IDFIFO
set +x
clear
cd "$DESTDIR" || {
echo "Could not cd into $DESTDIR"
exit 1
}
(
echo "Obsolete files..."
find . -type f \
| grep -v .timestamp \
| grep -v 00-INDEX.txt \
| while read filename
do
if [ ! -e "$ORIGDIR/${filename%.$FORMAT}".* ]
then
echo "Deleting $filename..."
rm -f "$filename"
fi
done
cd "$DESTDIR"
cd "$OLDPWD"
echo -e "\nEmpty directories..."
find "$DESTDIR" -type d -empty -print -delete
) | dialog --output-fd 1 --progressbox "Removing obsolete files..." $LINES $COLUMNS