live_set 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +71 -29
- data/als_notes.md +58 -0
- data/exe/als_delta +8 -0
- data/exe/live_set +1 -8
- data/lib/als_delta/ als_delta_class.rb +48 -0
- data/lib/als_delta/als_delta_options.rb +58 -0
- data/lib/common/common.rb +17 -0
- data/lib/common/run.rb +21 -0
- data/lib/common/util.rb +39 -0
- data/lib/live_set/live_set_all_tracks.rb +34 -0
- data/lib/live_set/live_set_audio_clip.rb +26 -0
- data/lib/live_set/live_set_audio_track.rb +34 -0
- data/lib/{llive_set_class.rb → live_set/live_set_class.rb} +11 -38
- data/lib/live_set/live_set_class_private.rb +20 -0
- data/lib/{options.rb → live_set/live_set_options.rb} +8 -7
- data/lib/live_set/live_set_scene.rb +9 -0
- data/lib/live_set/version.rb +1 -1
- data/lib/live_set.rb +41 -19
- data/live_set.gemspec +2 -1
- metadata +31 -6
- data/lib/common.rb +0 -18
- data/lib/util.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 383208bd53c5b54e254945faa8d79ae100b78a1dd34fa3820abdaec8dc1b2854
|
4
|
+
data.tar.gz: b6d6ae70303f7ff3a2c43039a27ca2424c8296dcf6cf36d3ff9a572d4d285b97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e2078593ba88bb503c9a2a712bc85362f05b31ceec83a3276785a3b01961d8c85b6c6cc3bf7822670e1e4bb4372dfb214225ab1cbe927f9e6ffab541539560a
|
7
|
+
data.tar.gz: 408994bfca9f20dec4df040cee40268da9339549963ee10da80725f2dc4f619ec01db53d1df4e0c02389aeee6224f8c7ecf1bb21044ce24878a56fb5177a2039
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
* Checks `Ableton Project Info` validity all up the directory tree.
|
6
|
+
* Prompts user if they want the extra directory renamed to `Ableton Project Info-`.
|
7
|
+
* Reports the clips in the set, and their sizes.
|
8
|
+
* Generates a warning if the total size of the clips might cause freezing to fail.
|
9
|
+
|
10
|
+
|
3
11
|
## 0.1.0
|
4
12
|
|
5
13
|
* Initial release.
|
data/README.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# `Live_set` [](https://badge.fury.io/rb/live_set)
|
2
2
|
|
3
|
-
This program can
|
4
|
-
|
3
|
+
This program can:
|
4
|
+
|
5
|
+
1. Report the version compatibility of an Ableton Live set.
|
6
|
+
2. Display the location and size of the samples in each track.
|
7
|
+
3. Shows frozen tracks.
|
8
|
+
4. Make a copy of an Ableton Live 12 set and save to Live 11 format.
|
9
|
+
5. Detect environmental problems, for example unwanted directories called `Ableton Project Info` in parent directories.
|
10
|
+
6. Detect sets that are too large to be frozen, which means they are too large to transfer to Push 3 Standalone.
|
11
|
+
7. Detect tracks that are not frozen, which means they are not ready to be transferred to Push 3 Standalone.
|
5
12
|
|
6
13
|
This program has successfully converted the Ableton Live 12 demo project to Live 11.
|
7
14
|
|
@@ -14,19 +21,24 @@ Simply type:
|
|
14
21
|
$ gem install live_set
|
15
22
|
```
|
16
23
|
|
24
|
+
If the above does not work because you need to install Ruby, please
|
25
|
+
[follow these instructions](https://www.mslinn.com/ruby/1000-ruby-setup.html).
|
26
|
+
|
17
27
|
|
18
28
|
## Usage
|
19
29
|
|
20
|
-
```
|
30
|
+
```text
|
21
31
|
$ live_set
|
22
|
-
Displays information about a Live set or converts a Live 12 set
|
32
|
+
Displays information about a Live set or converts a copy of a Live 12 set in Live 11 format.
|
33
|
+
Also verifies that 'Ableton Project Info' is in the same directory as the .als file,
|
34
|
+
and there is no parent directory with a directory of that name.
|
23
35
|
|
24
36
|
Syntax: live_set OPTIONS PATH_TO_ALS_FILE
|
25
37
|
|
26
38
|
Environment variables used in PATH_TO_ALS_FILE are expanded.
|
27
39
|
|
28
40
|
Options are:
|
29
|
-
-11 Convert the Live 12 set to Live 11 format.
|
41
|
+
-11 Convert a copy of the Live 12 set to Live 11 format.
|
30
42
|
-f Overwrite any existing Live 11 set.
|
31
43
|
-h Display this help message.
|
32
44
|
|
@@ -36,24 +48,53 @@ Example:
|
|
36
48
|
|
37
49
|
### Report
|
38
50
|
|
39
|
-
Report the version of the Ableton Live
|
40
|
-
|
41
|
-
```
|
42
|
-
$ live_set $
|
43
|
-
/mnt/
|
44
|
-
Created by Live
|
45
|
-
Major version
|
46
|
-
|
47
|
-
SchemaChangeCount
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
Report the version of the [Ableton Live 12 Demo Project](https://help.ableton.com/hc/en-us/articles/209774005-Location-of-the-default-Demo-Set):
|
52
|
+
|
53
|
+
```text
|
54
|
+
$ live_set "$ableton_media/Projects/Ableton Live 12 Demo Project/Ableton Live 12 Demo.als"
|
55
|
+
/mnt/e/media/Ableton/Projects/Ableton Live 12 Demo Project/Ableton Live 12 Demo.als
|
56
|
+
Created by Ableton Live 12.0
|
57
|
+
Major version 5
|
58
|
+
Minor version v12.0_12049
|
59
|
+
SchemaChangeCount 7
|
60
|
+
Revision 5094b92fa547974769f44cf233f1474777d9434a
|
61
|
+
Total set size: 312.5 MB
|
62
|
+
Warning: some tracks are not frozen, so this set should not be transferred to Push 3 Standalone.
|
63
|
+
4 tracks:
|
64
|
+
Track 'Vocal Main' (12 clips, totaling 154.0 MB)
|
65
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
66
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
67
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
68
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
69
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
70
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
71
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
72
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
73
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
74
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
75
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
76
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
77
|
+
|
78
|
+
Track 'Vocal Harmony' (12 clips, totaling 154.0 MB)
|
79
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
80
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
81
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
82
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
83
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
84
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
85
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
86
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
87
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
88
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
89
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
90
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
91
|
+
|
92
|
+
Track 'Vocal Hum' (1 clips, totaling 2.9 MB)
|
93
|
+
Samples/Imported/Vocal Patience Hum.wav 2.9 MB 2024-01-31 22:03:22
|
94
|
+
|
95
|
+
Track 'Vocal Adlib' (2 clips, totaling 1.7 MB)
|
96
|
+
Samples/Imported/Vocal Patience Adlib.wav 827.9 kB 2024-01-31 22:00:22
|
97
|
+
Samples/Imported/Vocal Patience Adlib.wav 827.9 kB 2024-01-31 22:00:22
|
57
98
|
```
|
58
99
|
|
59
100
|
|
@@ -68,18 +109,19 @@ $ live_set -11 -f path/to/my_set.als
|
|
68
109
|
|
69
110
|
## Development
|
70
111
|
|
71
|
-
After checking out this git repository, install dependencies by
|
112
|
+
After checking out this git repository, install dependencies by running `bin/setup`:
|
72
113
|
|
73
|
-
```
|
114
|
+
```text
|
115
|
+
$ git clone https://github.com/mslinn/live_set
|
116
|
+
$ cd live_set
|
74
117
|
$ bin/setup
|
118
|
+
$ code . # Run Visual Studio Code
|
75
119
|
```
|
76
120
|
|
77
|
-
You should do the above before running Visual Studio Code.
|
78
|
-
|
79
121
|
|
80
122
|
### Run the Tests
|
81
123
|
|
82
|
-
```
|
124
|
+
```text
|
83
125
|
$ bundle exec rake test
|
84
126
|
```
|
85
127
|
|
@@ -106,7 +148,7 @@ $ bin/console
|
|
106
148
|
|
107
149
|
To install this gem onto your local machine, type:
|
108
150
|
|
109
|
-
```
|
151
|
+
```text
|
110
152
|
$ bundle exec rake install
|
111
153
|
```
|
112
154
|
|
data/als_notes.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# ALS Filetype Notes
|
2
|
+
|
3
|
+
```xml
|
4
|
+
$ zcat '/mnt/e/media/Ableton/Projects/fu Project/fu.als' | less -N
|
5
|
+
3 <LiveSet>
|
6
|
+
8 <Tracks>
|
7
|
+
9 <AudioTrack Id="8">
|
8
|
+
44 <DeviceChain>
|
9
|
+
247 <MainSequencer>
|
10
|
+
278 <ClipSlotList>
|
11
|
+
279 <ClipSlot Id="14">
|
12
|
+
280 <LomId Value="0" />
|
13
|
+
281 <ClipSlot>
|
14
|
+
282 <Value>
|
15
|
+
283 <AudioClip Id="0" Time="0">
|
16
|
+
353 <SampleRef>
|
17
|
+
354 <FileRef>
|
18
|
+
355 <RelativePathType Value="3" />
|
19
|
+
356 <RelativePath Value="Samples/Imported/song1.mp3" />
|
20
|
+
357 <Path Value="E:/media/Ableton/Projects/fu Project/Samples/Imported/song1.mp3" />
|
21
|
+
358 <Type Value="1" />
|
22
|
+
359 <LivePackName Value="" />
|
23
|
+
360 <LivePackId Value="" />
|
24
|
+
361 <OriginalFileSize Value="3626186" /> # Bytes
|
25
|
+
362 <OriginalCrc Value="47766" />
|
26
|
+
363 </FileRef>
|
27
|
+
364 <LastModDate Value="1707598944" /> # Epoch timestamp -> Sat Feb 10 16:02:24 EST 2024
|
28
|
+
365 <SourceContext />
|
29
|
+
366 <SampleUsageHint Value="0" />
|
30
|
+
367 <DefaultDuration Value="10809120" />
|
31
|
+
368 <DefaultSampleRate Value="48000" />
|
32
|
+
369 </SampleRef>
|
33
|
+
```
|
34
|
+
|
35
|
+
|
36
|
+
```text
|
37
|
+
@audiotrack = LiveSet.Tracks.AudioTrack.
|
38
|
+
DeviceChain.MainSequencer.ClipSlotList.ClipSlot.children.
|
39
|
+
ClipSlot.Value.children. # Passed to LiveAudioClip
|
40
|
+
SampleRef.FileRef
|
41
|
+
Path['Value'] # Absolute path
|
42
|
+
RelativePath['Value']
|
43
|
+
OriginalFileSize['Value'] # Bytes
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
Clip paths can look like this:
|
48
|
+
|
49
|
+
```text
|
50
|
+
# @audio_track.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot
|
51
|
+
# @audio_track.DeviceChain.Mixer.ClipSlotList.ClipSlot
|
52
|
+
# @audio_track.xpath('//Path').map{|x| x['Value']}.uniq # Gives results like:
|
53
|
+
# "E:/media/Ableton/Projects/fu Project/Samples/Processed/Freeze/Freeze Guitar [2024-03-12 133812].wav"
|
54
|
+
# "E:/media/Ableton/Projects/fu Project/Samples/Imported/smooth_operator_horns.mp3"
|
55
|
+
# "/Reverb Default.adv"
|
56
|
+
# "/Users/nsh/Library/Application Support/Ableton/Live 11 Core Library/Devices/Audio Effects/Simple Delay/Dotted Eighth Note.adv"
|
57
|
+
# "E:/media/Ableton/Projects/fu Project/Audio Effects/Color Limiter/Ableton Folder Info/Color Limiter.amxd"
|
58
|
+
```
|
data/exe/als_delta
ADDED
data/exe/live_set
CHANGED
@@ -5,11 +5,4 @@ if ENV['VO_DEBUGGING']
|
|
5
5
|
else
|
6
6
|
require 'live_set'
|
7
7
|
end
|
8
|
-
|
9
|
-
require 'colorator'
|
10
|
-
require_relative '../lib/options'
|
11
|
-
|
12
|
-
def common(command)
|
13
|
-
@options = parse_options command
|
14
|
-
help_show 'Ableton Live set name must be provided.' if ARGV.empty?
|
15
|
-
end
|
8
|
+
live_set
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils'
|
3
|
+
require_relative '../common/run'
|
4
|
+
|
5
|
+
class AlsDelta
|
6
|
+
include Run
|
7
|
+
|
8
|
+
def initialize(set_name, **options)
|
9
|
+
@options = options
|
10
|
+
@loglevel = "-loglevel #{@options[:loglevel]}"
|
11
|
+
@loglevel += ' -stats' unless @options[:loglevel] == 'quiet'
|
12
|
+
@set_name = set_name
|
13
|
+
|
14
|
+
@set_dir = File.dirname set_name
|
15
|
+
@set_name_only = File.basename set_name, '.als'
|
16
|
+
end
|
17
|
+
|
18
|
+
def backup_name
|
19
|
+
@backup_name = if @options[:keep]
|
20
|
+
timestamp = Time.now.getutc.strftime '%Y-%m-%d_%H:%M:%S'
|
21
|
+
"#{@set_dir}/#{@set_name_only}_#{timestamp}.als"
|
22
|
+
else
|
23
|
+
"#{@set_dir}/#{@set_name_only}_backup.als"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def backup_set
|
28
|
+
@backup_name = backup_name
|
29
|
+
FileUtils.cp @set_name, @backup_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def cleanup
|
33
|
+
return if @options[:keep] || !@backup_name
|
34
|
+
return unless File.exist? @backup_name
|
35
|
+
|
36
|
+
puts "Deleting #{@backup_name}"
|
37
|
+
File.delete @backup_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def show
|
41
|
+
output = run_capture_stdout "zdiff '#{@backup_name}' '#{@set_name}'"
|
42
|
+
if output.empty?
|
43
|
+
puts 'There were no changes to the saved live set.'
|
44
|
+
else
|
45
|
+
puts output.join("\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'colorator'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze
|
5
|
+
|
6
|
+
def common(command)
|
7
|
+
@options = parse_options command
|
8
|
+
help_als_delta 'An Ableton Live set name must be provided.' if ARGV.empty?
|
9
|
+
end
|
10
|
+
|
11
|
+
def help_als_delta(msg = nil)
|
12
|
+
printf "Error: #{msg}\n\n".yellow unless msg.nil?
|
13
|
+
msg = <<~END_HELP
|
14
|
+
ls_delta: Shows changes to an .als file.
|
15
|
+
Press enter to compare with previous version.
|
16
|
+
|
17
|
+
Syntax: ls_delta OPTIONS PATH_TO_ALS_FILE
|
18
|
+
|
19
|
+
Environment variables used in PATH_TO_ALS_FILE are expanded.
|
20
|
+
|
21
|
+
Options are:
|
22
|
+
-k Keep each backup (default is to delete them)
|
23
|
+
-h Display this help message.
|
24
|
+
|
25
|
+
Example:
|
26
|
+
ls_delta $path/to/my_v12_set.als
|
27
|
+
END_HELP
|
28
|
+
printf msg.cyan
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_options
|
33
|
+
options = { keep: false, loglevel: 'warning' }
|
34
|
+
opts = do_parse
|
35
|
+
opts.order!(into: options)
|
36
|
+
|
37
|
+
help_als_delta "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
|
38
|
+
if options[:verbose] && !options[:verbose] in VERBOSITY
|
39
|
+
|
40
|
+
options
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def do_parse
|
46
|
+
OptionParser.new do |parser|
|
47
|
+
parser.program_name = File.basename __FILE__
|
48
|
+
@parser = parser
|
49
|
+
|
50
|
+
parser.on('-k', '--keep', 'Keep all backups')
|
51
|
+
parser.on('-l', '--loglevel LOGLEVEL', Integer, "Logging level (#{VERBOSITY.join ', '})")
|
52
|
+
parser.on('-v', '--verbose VERBOSE', 'Verbosity')
|
53
|
+
|
54
|
+
parser.on_tail('-h', '--help', 'Show this message') do
|
55
|
+
help_als_delta
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# require 'colorator'
|
2
|
+
|
3
|
+
# def common(command)
|
4
|
+
# @options = parse_options command
|
5
|
+
# help_live_set 'The path for the Ableton Set must be provided.' if ARGV.empty?
|
6
|
+
# end
|
7
|
+
|
8
|
+
# def show
|
9
|
+
# common :show
|
10
|
+
# help_live_set "Too many parameters specified.\n#{ARGV}" if ARGV.length > 2
|
11
|
+
|
12
|
+
# set_name = ARGV.join ' '
|
13
|
+
# help "#{set_name} does not exist" unless File.exist? set_name
|
14
|
+
# help "#{set_name} is a directory" if File.directory? set_name
|
15
|
+
|
16
|
+
# LiveSet.new(set_name, **@options).show
|
17
|
+
# end
|
data/lib/common/run.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Run
|
4
|
+
# @return true if the command succeeded
|
5
|
+
def run(command)
|
6
|
+
# printf "Run #{command}\n".yellow
|
7
|
+
$stdout.sync = true
|
8
|
+
system(*command)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return String array containing resulting lines of running command
|
12
|
+
def run_capture_stdout(command)
|
13
|
+
# printf "Run #{command}\n".yellow
|
14
|
+
stdout_str, status = Open3.capture2 command
|
15
|
+
unless status.success?
|
16
|
+
printf "Error: #{command} returned #{status}"
|
17
|
+
exit status.to_i
|
18
|
+
end
|
19
|
+
stdout_str.strip.split
|
20
|
+
end
|
21
|
+
end
|
data/lib/common/util.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Array
|
2
|
+
# Convert an Array of Array into a String, formatted as a table.
|
3
|
+
def columnize(separator = ' ')
|
4
|
+
each { |row| row.is_a? Array or raise NoMethodError, 'Must be called on an Array of Array.' }
|
5
|
+
result = ''
|
6
|
+
l = []
|
7
|
+
each { |row| row.each_with_index { |f, i| l[i] = [l[i] || 0, f.to_s.length].max } }
|
8
|
+
each do |row|
|
9
|
+
row.each_with_index { |f, i| result << "#{f.to_s.ljust l[i]}#{separator}" }
|
10
|
+
result << "\n"
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class String
|
17
|
+
def to_bool
|
18
|
+
return true if casecmp('true').zero?
|
19
|
+
return false if casecmp('false').zero?
|
20
|
+
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def expand_env
|
25
|
+
gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
|
26
|
+
ENV.fetch(Regexp.last_match(1), nil)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def human_file_size(number, decimal_places = 1)
|
32
|
+
ByteSize.new(number).to_s(decimal_places)
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_directory(dir)
|
36
|
+
Dir[File.join(dir, '*.rb')].sort.each do |file|
|
37
|
+
require file
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class AllTracks
|
2
|
+
def initialize(tracks)
|
3
|
+
@tracks = tracks
|
4
|
+
@track_instances = tracks.map { |track| LiveAudioTrack.new track }
|
5
|
+
end
|
6
|
+
|
7
|
+
def all_frozen
|
8
|
+
@track_instances.all?(&:frozen)
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
if @tracks.empty?
|
13
|
+
'No tracks'
|
14
|
+
else
|
15
|
+
summary + ' ' + @track_instances.map { |x| x.show_track(all_frozen) }.join("\n ") # rubocop:disable Style/StringConcatenation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def summary
|
20
|
+
all_frozen_msg = all_frozen ? ' (All are frozen)'.yellow : ''
|
21
|
+
push_warning = all_frozen ? '' : "\nWarning: some tracks are not frozen, so this set should not be transferred to Push 3 Standalone.".red
|
22
|
+
total_size = @track_instances.sum(&:track_size)
|
23
|
+
size_warning = if total_size >= 2_000_000_000
|
24
|
+
"\nWarning: This set is too large to be frozen and too large too large to transfer to Push 3 Standalone.".red
|
25
|
+
else
|
26
|
+
''
|
27
|
+
end
|
28
|
+
total_set_size = "Total set size: #{human_file_size total_size}".yellow
|
29
|
+
<<~END_MSG
|
30
|
+
#{total_set_size}#{size_warning}#{push_warning}
|
31
|
+
#{@track_instances.count} tracks#{all_frozen_msg}:
|
32
|
+
END_MSG
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class LiveAudioClip
|
4
|
+
attr_reader :file_size
|
5
|
+
|
6
|
+
# @param audio_clip is an inner <ClipSlot/> element from an Ableton Live .als file as parsed by Nokogiri
|
7
|
+
def initialize(id, need_refreeze, clip_slot)
|
8
|
+
@id = id
|
9
|
+
@need_refreeze = need_refreeze
|
10
|
+
|
11
|
+
@audio_clip = clip_slot.Value.AudioClip
|
12
|
+
@file_ref = @audio_clip.SampleRef.FileRef
|
13
|
+
@last_modified = Time.at(@audio_clip.SampleRef.LastModDate['Value'].to_i).utc.to_datetime
|
14
|
+
|
15
|
+
@absolute_path = @file_ref.Path['Value']
|
16
|
+
@file_size = @file_ref.OriginalFileSize['Value'].to_i
|
17
|
+
@file_type = @file_ref.Type['Value'] # What do these values mean?
|
18
|
+
@live_pack_name = @file_ref.LivePackName['Value']
|
19
|
+
@relative_path = @file_ref.RelativePath['Value']
|
20
|
+
@relative_path_type = @file_ref.RelativePathType['Value'] # What do these values mean?
|
21
|
+
end
|
22
|
+
|
23
|
+
def show_clip
|
24
|
+
[" #{@relative_path}", human_file_size(@file_size).rjust(8, ' '), @last_modified.strftime('%Y-%m-%d %H:%M:%S')]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class LiveAudioTrack
|
2
|
+
attr_reader :frozen, :track_size
|
3
|
+
|
4
|
+
# @param audio_track is an <AudioTrack/> element from an Ableton Live .als file as parsed by Nokogiri
|
5
|
+
def initialize(audio_track)
|
6
|
+
@audio_track = audio_track
|
7
|
+
@id = @audio_track['Id']
|
8
|
+
|
9
|
+
@frozen = @audio_track.Freeze['Value'].to_bool
|
10
|
+
|
11
|
+
@clip_slots = @audio_track.DeviceChain.MainSequencer.ClipSlotList
|
12
|
+
@audio_clips = @clip_slots.ClipSlot.map do |clip_slot|
|
13
|
+
clip_slot_id = clip_slot['Id']
|
14
|
+
need_refreeze = clip_slot.NeedRefreeze['Value'].to_bool
|
15
|
+
inner_clip_slot = clip_slot.ClipSlot
|
16
|
+
LiveAudioClip.new clip_slot_id, need_refreeze, inner_clip_slot if inner_clip_slot.respond_to?(:Value) && !inner_clip_slot.Value.children.empty?
|
17
|
+
end
|
18
|
+
@audio_clips.compact!
|
19
|
+
@track_size = @audio_clips.sum(&:file_size) || 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_track(all_frozen)
|
23
|
+
name = @audio_track.Name.EffectiveName['Value']
|
24
|
+
frozen = !all_frozen && @audio_track.frozen? ? ' **frozen**' : ''
|
25
|
+
"Track '#{name}'#{frozen} (#{@audio_clips.length} clips, totaling #{human_file_size @track_size})\n" + show_clips
|
26
|
+
end
|
27
|
+
|
28
|
+
def show_clips
|
29
|
+
return '' if @track_size.zero?
|
30
|
+
|
31
|
+
result = @audio_clips.map(&:show_clip)
|
32
|
+
result.columnize ' '
|
33
|
+
end
|
34
|
+
end
|
@@ -1,14 +1,11 @@
|
|
1
|
+
require 'bytesize'
|
1
2
|
require 'nokogiri'
|
3
|
+
require 'pathname'
|
2
4
|
require 'zlib'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
return false if casecmp('false').zero?
|
8
|
-
|
9
|
-
nil
|
10
|
-
end
|
11
|
-
end
|
6
|
+
# See https://www.ionos.ca/digitalguide/websites/web-development/xpath-tutorial/
|
7
|
+
# See https://www.scrapingbee.com/webscraping-questions/selenium/how-to-find-elements-by-xpath-selenium
|
8
|
+
# See https://nokogiri.org/tutorials/searching_a_xml_html_document.html#slop-1
|
12
9
|
|
13
10
|
class LiveSet
|
14
11
|
def initialize(set_name, **options)
|
@@ -21,9 +18,14 @@ class LiveSet
|
|
21
18
|
|
22
19
|
@ableton = @xml_doc.Ableton
|
23
20
|
@minor_version = @ableton['MinorVersion']
|
21
|
+
|
22
|
+
@live_set = @ableton.LiveSet
|
23
|
+
@tracks = AllTracks.new @live_set.Tracks.AudioTrack
|
24
|
+
@scenes = @live_set.Scenes.map { |scene| LiveScene.new scene }
|
24
25
|
end
|
25
26
|
|
26
27
|
def show
|
28
|
+
check_abelton_project_info
|
27
29
|
puts <<~END_SHOW
|
28
30
|
#{@set_name}
|
29
31
|
Created by #{@ableton['Creator']}
|
@@ -31,16 +33,8 @@ class LiveSet
|
|
31
33
|
Minor version v#{@minor_version}
|
32
34
|
SchemaChangeCount #{@ableton['SchemaChangeCount']}
|
33
35
|
Revision #{@ableton['Revision']}
|
36
|
+
#{@tracks.message}
|
34
37
|
END_SHOW
|
35
|
-
tracks = @ableton.LiveSet.Tracks.AudioTrack
|
36
|
-
track_msg = if tracks.empty?
|
37
|
-
'No tracks'
|
38
|
-
else
|
39
|
-
all_frozen = all_tracks_frozen(tracks)
|
40
|
-
track_summary(tracks) +
|
41
|
-
tracks.map { |x| show_track x, all_frozen }.join("\n ")
|
42
|
-
end
|
43
|
-
puts track_msg
|
44
38
|
end
|
45
39
|
|
46
40
|
def modify_als
|
@@ -72,25 +66,4 @@ class LiveSet
|
|
72
66
|
end
|
73
67
|
Zlib::GzipWriter.open(new_set_path) { |gz| gz.write new_contents }
|
74
68
|
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def all_tracks_frozen(tracks)
|
79
|
-
tracks.all? { |track| track_is_frozen track }
|
80
|
-
end
|
81
|
-
|
82
|
-
def track_is_frozen(track)
|
83
|
-
track.Freeze['Value'].to_bool
|
84
|
-
end
|
85
|
-
|
86
|
-
def track_summary(tracks)
|
87
|
-
all_frozen_msg = all_tracks_frozen(tracks) ? ' (All are frozen)' : ''
|
88
|
-
"#{tracks.count} tracks#{all_frozen_msg}:\n "
|
89
|
-
end
|
90
|
-
|
91
|
-
def show_track(audio_track, all_frozen)
|
92
|
-
name = audio_track.Name.EffectiveName['Value']
|
93
|
-
frozen = !all_frozen && track_is_frozen(audio_track) ? ' **frozen**' : ''
|
94
|
-
"#{name}#{frozen}"
|
95
|
-
end
|
96
69
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class LiveSet
|
2
|
+
private
|
3
|
+
|
4
|
+
def check_abelton_project_info
|
5
|
+
set_directory = File.realpath(File.dirname(@set_name))
|
6
|
+
api_path = File.join(set_directory, 'Ableton Project Info')
|
7
|
+
puts "Warning: '#{api_path}' is not present".red unless File.exist? api_path
|
8
|
+
puts "Warning: '#{api_path}' is not a directory".red unless Dir.exist? api_path
|
9
|
+
|
10
|
+
cur_dir = Pathname.new(set_directory).parent
|
11
|
+
while cur_dir
|
12
|
+
if File.exist? File.join(cur_dir, 'Ableton Project Info')
|
13
|
+
puts "Warning: 'Ableton Project Info' exists in parent directory '#{cur_dir.to_path}'".red
|
14
|
+
end
|
15
|
+
break if cur_dir.to_path == '/'
|
16
|
+
|
17
|
+
cur_dir = cur_dir.parent
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,10 +3,12 @@ require 'optparse'
|
|
3
3
|
|
4
4
|
VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze
|
5
5
|
|
6
|
-
def
|
6
|
+
def live_set(msg = nil)
|
7
7
|
printf "Error: #{msg}\n\n".yellow unless msg.nil?
|
8
8
|
msg = <<~END_HELP
|
9
|
-
|
9
|
+
Live_sete displays information about an Ableton Live set or converts a Live 12 set to Live 11 format.
|
10
|
+
If the special folder called 'Ableton Project Info' is not present in the same folder as the .als file, a warning is generated.
|
11
|
+
Similarly, if 'Ableton Project Info' is present in any parent folder, a warning is issued.
|
10
12
|
|
11
13
|
Syntax: live_set OPTIONS PATH_TO_ALS_FILE
|
12
14
|
|
@@ -14,7 +16,7 @@ def help_show(msg = nil)
|
|
14
16
|
|
15
17
|
Options are:
|
16
18
|
-11 Convert the Live 12 set to Live 11 format.
|
17
|
-
-f
|
19
|
+
-f If -11 is specified, overwrite any existing Live 11 set.
|
18
20
|
-h Display this help message.
|
19
21
|
|
20
22
|
Example:
|
@@ -29,13 +31,12 @@ def parse_options
|
|
29
31
|
opts = do_parse
|
30
32
|
opts.order!(into: options)
|
31
33
|
|
32
|
-
|
34
|
+
live_set "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
|
33
35
|
if options[:verbose] && !options[:verbose] in VERBOSITY
|
34
36
|
|
35
37
|
options
|
36
38
|
end
|
37
39
|
|
38
|
-
|
39
40
|
private
|
40
41
|
|
41
42
|
def do_parse
|
@@ -47,10 +48,10 @@ def do_parse
|
|
47
48
|
parser.on('-f', '--force', 'Overwrite the output set if it already exists')
|
48
49
|
parser.on('-s', '--show', 'Display information about the Ableton Live set')
|
49
50
|
parser.on('-l', '--loglevel LOGLEVEL', Integer, "Logging level (#{VERBOSITY.join ', '})")
|
50
|
-
parser.on('-v', '--verbose VERBOSE', '
|
51
|
+
parser.on('-v', '--verbose VERBOSE', 'Verbosity')
|
51
52
|
|
52
53
|
parser.on_tail('-h', '--help', 'Show this message') do
|
53
|
-
|
54
|
+
live_set
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class LiveScene
|
2
|
+
# @param live_scene is an XML element as parsed by Nokogiri
|
3
|
+
def initialize(live_scene)
|
4
|
+
@live_scene = live_scene
|
5
|
+
@id = @live_scene['Id']
|
6
|
+
@name = @live_scene.Name['Value']
|
7
|
+
@tempo = @live_scene.Tempo['Value'] if @live_scene.IsTempoEnabled['Value'].to_bool
|
8
|
+
end
|
9
|
+
end
|
data/lib/live_set/version.rb
CHANGED
data/lib/live_set.rb
CHANGED
@@ -1,29 +1,51 @@
|
|
1
|
-
require_relative 'live_set/
|
1
|
+
require_relative 'live_set/live_set_options'
|
2
|
+
require_relative 'common/util'
|
3
|
+
require_directory "#{__dir__}/common"
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require file unless file.end_with?('/live_set.rb')
|
5
|
+
def common(command)
|
6
|
+
@options = parse_options command
|
7
|
+
help_live_set 'Ableton Live set name must be provided.' if ARGV.empty?
|
7
8
|
end
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def als_delta
|
11
|
+
require_directory "#{__dir__}/als_delta"
|
12
|
+
|
13
|
+
help_als_delta if ARGV.empty?
|
14
|
+
|
15
|
+
@options = parse_options
|
16
|
+
|
17
|
+
set_name = ARGV.join(' ').expand_env
|
18
|
+
help_als_delta "#{set_name} does not exist" unless File.exist? set_name
|
19
|
+
help_als_delta "#{set_name} is a directory" if File.directory? set_name
|
20
|
+
|
21
|
+
trap('SIGINT') { throw :ctrl_c }
|
22
|
+
|
23
|
+
als_delta = AlsDelta.new set_name, **@options
|
24
|
+
catch :ctrl_c do
|
25
|
+
als_delta.backup_set
|
26
|
+
als_delta.show
|
27
|
+
als_delta.cleanup
|
28
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
29
|
+
als_delta.cleanup
|
30
|
+
end
|
13
31
|
end
|
14
32
|
|
15
|
-
|
33
|
+
def live_set
|
34
|
+
require_directory "#{__dir__}/live_set"
|
35
|
+
|
36
|
+
help_live_set if ARGV.empty?
|
16
37
|
|
17
|
-
@options = parse_options
|
38
|
+
@options = parse_options
|
18
39
|
|
19
|
-
set_name =
|
20
|
-
|
21
|
-
|
40
|
+
set_name = ARGV.join(' ').expand_env
|
41
|
+
help_live_set "#{set_name} does not exist" unless File.exist? set_name
|
42
|
+
help_live_set "#{set_name} is a directory" if File.directory? set_name
|
22
43
|
|
23
|
-
live_set = LiveSet.new set_name, **@options
|
44
|
+
live_set = LiveSet.new set_name, **@options
|
24
45
|
|
25
|
-
if @options[:convert11]
|
26
|
-
|
27
|
-
else
|
28
|
-
|
46
|
+
if @options[:convert11]
|
47
|
+
live_set.modify_als
|
48
|
+
else
|
49
|
+
live_set.show
|
50
|
+
end
|
29
51
|
end
|
data/live_set.gemspec
CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |spec|
|
|
5
5
|
|
6
6
|
spec.authors = ['Mike Slinn']
|
7
7
|
spec.bindir = 'exe'
|
8
|
-
spec.executables = %w[live_set]
|
8
|
+
spec.executables = %w[als_delta live_set]
|
9
9
|
spec.description = <<~END_DESC
|
10
10
|
Reports the version of an Ableton Live set, and optionally changes it to v11 format.
|
11
11
|
END_DESC
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.summary = 'Reports the version of an Ableton Live set, and optionally changes it to v11 format.'
|
32
32
|
spec.version = LiveSetVersion::VERSION
|
33
33
|
|
34
|
+
spec.add_dependency 'bytesize'
|
34
35
|
spec.add_dependency 'colorator'
|
35
36
|
spec.add_dependency 'nokogiri'
|
36
37
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: live_set
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Slinn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bytesize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: colorator
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,6 +59,7 @@ description: 'Reports the version of an Ableton Live set, and optionally changes
|
|
45
59
|
email:
|
46
60
|
- mslinn@mslinn.com
|
47
61
|
executables:
|
62
|
+
- als_delta
|
48
63
|
- live_set
|
49
64
|
extensions: []
|
50
65
|
extra_rdoc_files: []
|
@@ -53,13 +68,23 @@ files:
|
|
53
68
|
- CHANGELOG.md
|
54
69
|
- README.md
|
55
70
|
- Rakefile
|
71
|
+
- als_notes.md
|
72
|
+
- exe/als_delta
|
56
73
|
- exe/live_set
|
57
|
-
- lib/
|
74
|
+
- lib/als_delta/ als_delta_class.rb
|
75
|
+
- lib/als_delta/als_delta_options.rb
|
76
|
+
- lib/common/common.rb
|
77
|
+
- lib/common/run.rb
|
78
|
+
- lib/common/util.rb
|
58
79
|
- lib/live_set.rb
|
80
|
+
- lib/live_set/live_set_all_tracks.rb
|
81
|
+
- lib/live_set/live_set_audio_clip.rb
|
82
|
+
- lib/live_set/live_set_audio_track.rb
|
83
|
+
- lib/live_set/live_set_class.rb
|
84
|
+
- lib/live_set/live_set_class_private.rb
|
85
|
+
- lib/live_set/live_set_options.rb
|
86
|
+
- lib/live_set/live_set_scene.rb
|
59
87
|
- lib/live_set/version.rb
|
60
|
-
- lib/llive_set_class.rb
|
61
|
-
- lib/options.rb
|
62
|
-
- lib/util.rb
|
63
88
|
- live_set.gemspec
|
64
89
|
homepage: https://github.com/mslinn/live_set
|
65
90
|
licenses:
|
data/lib/common.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'colorator'
|
2
|
-
require_relative 'options'
|
3
|
-
|
4
|
-
def common(command)
|
5
|
-
@options = parse_options command
|
6
|
-
help_show 'The path for the Ableton Set must be provided.' if ARGV.empty?
|
7
|
-
end
|
8
|
-
|
9
|
-
def show
|
10
|
-
common :show
|
11
|
-
help_show "Too many parameters specified.\n#{ARGV}" if ARGV.length > 2
|
12
|
-
|
13
|
-
set_name = ARGV.join ' '
|
14
|
-
help "#{set_name} does not exist" unless File.exist? set_name
|
15
|
-
help "#{set_name} is a directory" if File.directory? set_name
|
16
|
-
|
17
|
-
LiveSet.new(set_name, **@options).show
|
18
|
-
end
|