live_set 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40ccc8116bfe826b52087a95ccdda582b948c7312be95eb282f17c5da7a24c86
4
- data.tar.gz: e97386a24aa9dbcee91080e07cd2c9cc4331b65dab5b5080cbd0665b058de37a
3
+ metadata.gz: 9caef58c0bed30ead696f9d64848c74038aa520d83a2c47796cb0081c0ad6b14
4
+ data.tar.gz: 85906715baa37c0ed5a2fd4f0a101e37d5c5728a4385f0754d120514b18d7b89
5
5
  SHA512:
6
- metadata.gz: 90def6a620d141ee67449c7c4f66345d65aea8cad97a5ce0a37a8095e223b2ffc214825a5c10e81aa96e62c141fb6ba0df05cc0f3bb56bbf9f115238cad4fed3
7
- data.tar.gz: 1f2e4ffa6e835d7021fe5e2d124a758b9cc629071cc1f5d1517cf20915640932966a433f867b92dc1083261aecb8b8fc748c5031dadaef0935a327af4ef9760a
6
+ metadata.gz: 88404bf0b8d04663da0d7a334a8affd9d930df965af1b3fed1b3a2b9286881380ecba7882badbf71d0612674a3f77775a436a6e5a505a4bb9b25801522ad5ab6
7
+ data.tar.gz: d633040d514b77e4984e936c4676fd5f124ee30da405b6c612f916edbd33625cbacb0a1d35812aaae393f03436cab2054144e54a75db1214ba64196c92511cf4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Change Log
2
2
 
3
+
4
+ ## 0.2.1
5
+
6
+ * Corrected missing help method for the `live_set` command.
7
+ * Prompts user if they want the extra directory renamed to `Ableton Project Info-`.
8
+
9
+
10
+ ## 0.2.0
11
+
12
+ * Checks `Ableton Project Info` validity all up the directory tree.
13
+ * Reports the clips in the set, and their sizes.
14
+ * Generates a warning if the total size of the clips might cause freezing to fail.
15
+
16
+
3
17
  ## 0.1.0
4
18
 
5
19
  * Initial release.
data/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # `Live_set` [![Gem Version](https://badge.fury.io/rb/live_set.svg)](https://badge.fury.io/rb/live_set)
2
2
 
3
- This program can report the version of an Ableton Live set,
4
- and it can changes an Ableton Live set from Live 12 format to Live 11 format.
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
- ```shell
30
+ ```text
21
31
  $ live_set
22
- Displays information about a Live set or converts a Live 12 set to Live 11 format.
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 set:
40
-
41
- ```shell
42
- $ live_set $path/to/my_set.als
43
- /mnt/c/media/Ableton/Projects/fu Project/fu.als
44
- Created by Live v11.0_11300
45
- Major version 11.0_11300
46
- Revision 5ac24cad7c51ea0671d49e6b4885371f15b57c1e
47
- SchemaChangeCount 3
48
- Tracks:
49
- Drums / complete
50
- Bass
51
- Guitar
52
- Horns
53
- Keys
54
- Vocal Backing
55
- Vocal Lead
56
- Strings
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 typing:
112
+ After checking out this git repository, install dependencies by running `bin/setup`:
72
113
 
73
- ```shell
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
- ```shell
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
- ```shell
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
@@ -0,0 +1,8 @@
1
+ #!/bin/env ruby
2
+
3
+ if ENV['VO_DEBUGGING']
4
+ require_relative '../lib/live_set'
5
+ else
6
+ require 'live_set'
7
+ end
8
+ als_delta
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,60 @@
1
+ require 'date'
2
+ require 'fileutils'
3
+ require 'highline'
4
+ require 'shellwords'
5
+ require_relative '../common/run'
6
+
7
+ class AlsDelta
8
+ include Run
9
+
10
+ def initialize(set_name, **options)
11
+ @options = options
12
+ @loglevel = "-loglevel #{@options[:loglevel]}"
13
+ @loglevel += ' -stats' unless @options[:loglevel] == 'quiet'
14
+ @set_name = set_name
15
+
16
+ @set_dir = File.dirname set_name
17
+ @set_name_only = File.basename set_name, '.als'
18
+ end
19
+
20
+ def backup_name
21
+ @backup_name = if @options[:keep]
22
+ timestamp = Time.now.getutc.strftime '%Y-%m-%d_%H:%M:%S'
23
+ "#{@set_dir}/#{@set_name_only}_#{timestamp}.als"
24
+ else
25
+ "#{@set_dir}/#{@set_name_only}_backup.als"
26
+ end
27
+ end
28
+
29
+ def backup_set
30
+ @backup_name = backup_name
31
+ puts "Backing up Live set to '#{@backup_name}'"
32
+ FileUtils.cp @set_name, @backup_name
33
+ end
34
+
35
+ def cleanup
36
+ return if @options[:keep] || !@backup_name
37
+ return unless File.exist? @backup_name
38
+
39
+ puts "Deleting '#{@backup_name}'"
40
+ File.delete @backup_name
41
+ end
42
+
43
+ def show
44
+ backup_set
45
+ loop do
46
+ puts 'Press any key to display the changes to the Live set XML file, or press Esc to exit.'
47
+ character = $stdin.getch
48
+ exit if character.ord == 27
49
+ output = run_capture_stdout "GZIP=--no-name zdiff #{@set_name.shellescape} #{@backup_name.shellescape}"
50
+ if output.empty?
51
+ puts 'There were no changes to the saved live set.'
52
+ else
53
+ puts output.join("\n")
54
+ backup_set
55
+ end
56
+ rescue StandardError => e
57
+ puts e
58
+ end
59
+ end
60
+ 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 unless defined? VERBOSITY
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
@@ -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
- class String
5
- def to_bool
6
- return true if casecmp('true').zero?
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,29 @@
1
+ require 'colorator'
2
+ require 'highline'
3
+
4
+ class LiveSet
5
+ private
6
+
7
+ def check_abelton_project_info
8
+ set_directory = File.realpath(File.dirname(@set_name))
9
+ api_path = File.join(set_directory, 'Ableton Project Info')
10
+ puts "Warning: '#{api_path}' is not present".red unless File.exist? api_path
11
+ puts "Warning: '#{api_path}' is not a directory".red unless Dir.exist? api_path
12
+
13
+ cur_dir = Pathname.new(set_directory).parent
14
+ while cur_dir
15
+ project_info_path = File.join(cur_dir, 'Ableton Project Info')
16
+ add_dash_suffix(project_info_path) if File.exist? project_info_path
17
+ break if cur_dir.to_path == '/'
18
+
19
+ cur_dir = cur_dir.parent
20
+ end
21
+ end
22
+
23
+ def add_dash_suffix(cur_dir, path)
24
+ puts "Warning: 'Ableton Project Info' exists in parent directory '#{cur_dir.to_path}'".red
25
+ return unless HighLine.agree("\nDo you want the directory to be disabled by suffixing a dash to its name? ", character = true)
26
+
27
+ File.rename path, "#{path}-"
28
+ end
29
+ end
@@ -1,12 +1,14 @@
1
1
  require 'colorator'
2
2
  require 'optparse'
3
3
 
4
- VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze
4
+ VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze unless defined? VERBOSITY
5
5
 
6
- def help_show(msg = nil)
6
+ def help_live_set(msg = nil)
7
7
  printf "Error: #{msg}\n\n".yellow unless msg.nil?
8
8
  msg = <<~END_HELP
9
- Displays information about a Live set or converts a Live 12 set to Live 11 format.
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 Overwrite any existing Live 11 set.
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
- help_show "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
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', 'Zoom percentage')
51
+ parser.on('-v', '--verbose VERBOSE', 'Verbosity')
51
52
 
52
53
  parser.on_tail('-h', '--help', 'Show this message') do
53
- help_show
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
@@ -1,3 +1,3 @@
1
1
  module LiveSetVersion
2
- VERSION = '0.1.0'.freeze unless defined? VERSION
2
+ VERSION = '0.2.1'.freeze unless defined? VERSION
3
3
  end
data/lib/live_set.rb CHANGED
@@ -1,29 +1,50 @@
1
- require_relative 'live_set/version'
1
+ require_relative 'live_set/live_set_options'
2
+ require_relative 'common/util'
3
+ require_directory "#{__dir__}/common"
2
4
 
3
- # Require all Ruby files in 'lib/', except this file
4
- Dir[File.join(__dir__, '*.rb')].sort.each do |file|
5
- # puts "Requiring #{file}"
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 help(msg = nil)
10
- puts "Error: #{msg}" if msg
11
- puts 'List files in ableton set'
12
- exit 1
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.show
26
+ als_delta.cleanup
27
+ rescue Exception # rubocop:disable Lint/RescueException
28
+ als_delta.cleanup
29
+ end
13
30
  end
14
31
 
15
- help_show if ARGV.empty?
32
+ def live_set
33
+ require_directory "#{__dir__}/live_set"
34
+
35
+ help_live_set if ARGV.empty?
16
36
 
17
- @options = parse_options
37
+ @options = parse_options
18
38
 
19
- set_name = expand_env ARGV.join(' ')
20
- help_show "#{set_name} does not exist" unless File.exist? set_name
21
- help_show "#{set_name} is a directory" if File.directory? set_name
39
+ set_name = ARGV.join(' ').expand_env
40
+ help_live_set "#{set_name} does not exist" unless File.exist? set_name
41
+ help_live_set "#{set_name} is a directory" if File.directory? set_name
22
42
 
23
- live_set = LiveSet.new set_name, **@options
43
+ live_set = LiveSet.new set_name, **@options
24
44
 
25
- if @options[:convert11]
26
- live_set.modify_als
27
- else
28
- live_set.show
45
+ if @options[:convert11]
46
+ live_set.modify_als
47
+ else
48
+ live_set.show
49
+ end
29
50
  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,8 @@ 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'
36
+ spec.add_dependency 'highline'
35
37
  spec.add_dependency 'nokogiri'
36
38
  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.1.0
4
+ version: 0.2.1
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-14 00:00:00.000000000 Z
11
+ date: 2024-03-21 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
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - ">="
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: highline
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: nokogiri
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -45,6 +73,7 @@ description: 'Reports the version of an Ableton Live set, and optionally changes
45
73
  email:
46
74
  - mslinn@mslinn.com
47
75
  executables:
76
+ - als_delta
48
77
  - live_set
49
78
  extensions: []
50
79
  extra_rdoc_files: []
@@ -53,13 +82,23 @@ files:
53
82
  - CHANGELOG.md
54
83
  - README.md
55
84
  - Rakefile
85
+ - als_notes.md
86
+ - exe/als_delta
56
87
  - exe/live_set
57
- - lib/common.rb
88
+ - lib/als_delta/als_delta_class.rb
89
+ - lib/als_delta/als_delta_options.rb
90
+ - lib/common/common.rb
91
+ - lib/common/run.rb
92
+ - lib/common/util.rb
58
93
  - lib/live_set.rb
94
+ - lib/live_set/live_set_all_tracks.rb
95
+ - lib/live_set/live_set_audio_clip.rb
96
+ - lib/live_set/live_set_audio_track.rb
97
+ - lib/live_set/live_set_class.rb
98
+ - lib/live_set/live_set_class_private.rb
99
+ - lib/live_set/live_set_options.rb
100
+ - lib/live_set/live_set_scene.rb
59
101
  - lib/live_set/version.rb
60
- - lib/llive_set_class.rb
61
- - lib/options.rb
62
- - lib/util.rb
63
102
  - live_set.gemspec
64
103
  homepage: https://github.com/mslinn/live_set
65
104
  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
data/lib/util.rb DELETED
@@ -1,5 +0,0 @@
1
- def self.expand_env(str)
2
- str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
3
- ENV.fetch(Regexp.last_match(1), nil)
4
- end
5
- end