cue_breaker 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd9d8859514a4edf0131c5f6e9c01939df0ee6cfe0389ff909e77c7a3e0828a6
4
- data.tar.gz: 18c39dd44c5e26ced3b3a232ed521a499453a4ded92d1fb92cdd6b6d303d2875
3
+ metadata.gz: 0f81daf932fcd49508c2ebd0e7959cba52eae031b6ad2414d62b1665784256c8
4
+ data.tar.gz: eceb7224f2c5cfaa88c1f36b3952c294c4235b51663dad387cd8e8d7f03e32c2
5
5
  SHA512:
6
- metadata.gz: b7e39bcf1aa41da85fb871a4e4f9975a9bdc9fd45f68c4fc0b5ea221b1fcc7211befb2be4bfcac66ef997c1123e0052bbe9a3f726a557b1708d69c81d1dfa2cf
7
- data.tar.gz: ba7b880ae0588c66c32f445c82959e0b07741f22f300150f77e262f1495d56c050cf6872909a1d40a9043481c48d30a1474f0361afc4f8bda71bb71ac128846d
6
+ metadata.gz: feaa1224b9f477473f8eb2a5ff6d42ff2af6e3b0b1b95072388342e229fe5faa655a8bfaef60be8add7f2d79892c02c6901dc95fbe36ebff943a7d32a932edbf
7
+ data.tar.gz: 808f0efc30ea766782ea4b8fbe1d506c8f83d202b0eac336a76d309b094b74a77f05ae6bb998a4807e20216de3605f29bd2cfe3c9563247b4d988dd063250e00
data/bin/cue_break CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/local/bin/ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'bundler/setup'
4
+ require "bundler/setup"
4
5
 
5
- require 'cue_breaker'
6
+ require "cue_breaker"
6
7
 
7
8
  CueBreaker::CLI.start
@@ -1,11 +1,17 @@
1
- require 'delegate'
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
2
4
 
3
5
  module CueBreaker
4
6
  class Album
5
7
  attr_reader :title, :performer, :genre, :total_tracks, :date
6
8
 
7
9
  def initialize(title:, performer:, genre:, total_tracks:, date:)
8
- @title, @performer, @genre, @total_tracks, @date = title, performer, genre, total_tracks, date
10
+ @title = title
11
+ @performer = performer
12
+ @genre = genre
13
+ @total_tracks = total_tracks
14
+ @date = date
9
15
  end
10
16
 
11
17
  def present
@@ -18,7 +24,7 @@ module CueBreaker
18
24
  end
19
25
 
20
26
  def genre
21
- super&.gsub(/[<>:"\/\\|?*]/, '').to_s
27
+ super&.gsub(%r{[<>:"/\\|?*]}, "").to_s
22
28
  end
23
29
  end
24
30
  end
@@ -1,9 +1,11 @@
1
- require 'optparse'
2
- require 'delegate'
1
+ # frozen_string_literal: true
3
2
 
4
- require 'cue_breaker/version'
5
- require 'cue_breaker/core'
6
- require 'cue_breaker/dependencies'
3
+ require "optparse"
4
+ require "delegate"
5
+
6
+ require "cue_breaker/version"
7
+ require "cue_breaker/core"
8
+ require "cue_breaker/dependencies"
7
9
 
8
10
  module CueBreaker
9
11
  module CLI
@@ -30,18 +32,20 @@ module CueBreaker
30
32
  options = {}
31
33
 
32
34
  OptionParser.new do |parser|
33
- parser.banner = <<~BANNER
34
- cue_break version #{VERSION}
35
- Usage: cue_break [options]
36
- BANNER
37
-
38
- parser.on('-c', '--cue CUEFILE') { |o| options[:cue] = o }
39
- parser.on('-w', '--wav WAVFILE') { |o| options[:wav] = o }
40
- parser.on('-o', '--output OUTPUT') { |o| options[:output] = o }
35
+ parser.banner = BANNER
36
+
37
+ parser.on("-c", "--cue CUEFILE") { |o| options[:cue] = o }
38
+ parser.on("-w", "--wav WAVFILE") { |o| options[:wav] = o }
39
+ parser.on("-o", "--output OUTPUT") { |o| options[:output] = o }
41
40
  end.parse!
42
41
 
43
- super Struct.new(*options.keys).new(*options.values)
42
+ super(Struct.new(*options.keys).new(*options.values))
44
43
  end
44
+
45
+ BANNER = <<~BANNER.freeze
46
+ cue_break version #{VERSION}
47
+ Usage: cue_break [options]
48
+ BANNER
45
49
  end
46
50
  end
47
51
  end
@@ -1,17 +1,19 @@
1
- require 'rubycue'
2
- require 'fileutils'
3
- require 'open3'
1
+ # frozen_string_literal: true
4
2
 
5
- require 'cue_breaker/album'
6
- require 'cue_breaker/track'
7
- require 'cue_breaker/ffmpeg_args'
3
+ require "fileutils"
4
+ require "open3"
5
+
6
+ require "cue_breaker/album"
7
+ require "cue_breaker/track"
8
+ require "cue_breaker/ffmpeg_args"
9
+ require "cue_breaker/cue/sheet"
8
10
 
9
11
  module CueBreaker
10
12
  module Core
11
13
  extend self
12
14
 
13
15
  def parse_cue(cue_file, duration:, &block)
14
- cue_sheet = RubyCue::Cuesheet.new(File.read(cue_file), duration.to_i)
16
+ cue_sheet = Cue::Sheet.new(File.read(cue_file), duration.to_i)
15
17
  cue_sheet.parse!
16
18
 
17
19
  album = as_album(cue_sheet)
@@ -23,16 +25,16 @@ module CueBreaker
23
25
 
24
26
  def convert_to_mp3(wav_file, album, track, output_dir)
25
27
  output_path = File.join(output_dir, sanitize_file_name(album.title))
26
- FileUtils.mkdir_p(output_path) unless Dir.exist?(output_path)
28
+ FileUtils.mkdir_p(output_path)
27
29
 
28
30
  ffmpeger = FFmpegArgs.new(wav_file, album, track)
29
- system('ffmpeg', *ffmpeger.options(output_path))
31
+ system("ffmpeg", *ffmpeger.options(output_path))
30
32
  puts "Exported: #{ffmpeger.output_file_path(output_path)}"
31
33
  end
32
34
 
33
35
  def get_audio_duration(file_path)
34
36
  stdout, stderr, status = Open3.capture3(<<~CMD)
35
- ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"#{file_path}\"
37
+ ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "#{file_path}"
36
38
  CMD
37
39
 
38
40
  raise "Error getting duration: #{stderr}" unless status.success?
@@ -62,7 +64,7 @@ module CueBreaker
62
64
  end
63
65
 
64
66
  def sanitize_file_name(name)
65
- name.gsub(%r{[<>:"/\\|?*]}, '')
67
+ name.gsub(%r{[<>:"/\\|?*]}, "")
66
68
  end
67
69
  end
68
70
  end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CueBreaker
4
+ module Cue
5
+ class Index
6
+ include Enumerable
7
+
8
+ SECONDS_PER_MINUTE = 60
9
+ FRAMES_PER_SECOND = 75
10
+ FRAMES_PER_MINUTE = FRAMES_PER_SECOND * 60
11
+
12
+ attr_reader :minutes, :seconds, :frames
13
+
14
+ class << self
15
+ def parse_from(value)
16
+ case value
17
+ when Array
18
+ from_list(value)
19
+ when Integer
20
+ from_integer(value)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def from_list(array)
27
+ if array.size != 3 || array.any? { |element| !element.is_a?(Integer) }
28
+ raise ArgumentError,
29
+ "Must be initialized with an array in the format of [minutes, seconds,frames], all integers"
30
+ end
31
+
32
+ array
33
+ end
34
+
35
+ def from_integer(seconds)
36
+ minutes = 0
37
+ frames = 0
38
+
39
+ while seconds >= SECONDS_PER_MINUTE
40
+ minutes += 1
41
+ seconds -= SECONDS_PER_MINUTE
42
+ end
43
+
44
+ [minutes, seconds, frames]
45
+ end
46
+ end
47
+
48
+ def initialize(value = nil)
49
+ @minutes, @seconds, @frames = *self.class.parse_from(value)
50
+ end
51
+
52
+ def to_f
53
+ ((@minutes * SECONDS_PER_MINUTE) + @seconds + (@frames.to_f / FRAMES_PER_SECOND)).to_f
54
+ end
55
+
56
+ def to_i
57
+ to_f.floor
58
+ end
59
+
60
+ def to_a
61
+ [@minutes, @seconds, @frames]
62
+ end
63
+
64
+ def to_s
65
+ "#{format('%02d', @minutes)}:#{format('%02d', @seconds)}:#{format('%02d', @frames)}"
66
+ end
67
+
68
+ def +(other)
69
+ self.class.new(carrying_addition(other))
70
+ end
71
+
72
+ def -(other)
73
+ self.class.new(carrying_subtraction(other))
74
+ end
75
+
76
+ def >(other)
77
+ to_f > other.to_f
78
+ end
79
+
80
+ def <(other)
81
+ to_f < other.to_f
82
+ end
83
+
84
+ def ==(other)
85
+ to_a == other.to_a
86
+ end
87
+
88
+ def each(&block)
89
+ to_a.each(&block)
90
+ end
91
+
92
+ private
93
+
94
+ def carrying_addition(other)
95
+ minutes = @minutes + other.minutes
96
+ seconds = @seconds + other.seconds
97
+ frames = @frames + other.frames
98
+
99
+ seconds, frames = *convert_with_rate(frames, seconds, FRAMES_PER_SECOND)
100
+ minutes, seconds = *convert_with_rate(seconds, minutes, SECONDS_PER_MINUTE)
101
+ [minutes, seconds, frames]
102
+ end
103
+
104
+ def carrying_subtraction(other)
105
+ seconds = minutes = 0
106
+
107
+ my_frames = @frames + (@seconds * FRAMES_PER_SECOND) + (@minutes * FRAMES_PER_MINUTE)
108
+ other_frames = other.frames + (other.seconds * FRAMES_PER_SECOND) + (other.minutes * FRAMES_PER_MINUTE)
109
+ frames = my_frames - other_frames
110
+
111
+ seconds, frames = *convert_with_rate(frames, seconds, FRAMES_PER_SECOND)
112
+ minutes, seconds = *convert_with_rate(seconds, minutes, SECONDS_PER_MINUTE)
113
+ [minutes, seconds, frames]
114
+ end
115
+
116
+ def convert_with_rate(from, to, rate, step = 1)
117
+ while from >= rate
118
+ to += step
119
+ from -= rate
120
+ end
121
+ [to, from]
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cue_breaker/cue/index"
4
+
5
+ module CueBreaker
6
+ module Cue
7
+ class Sheet
8
+ attr_reader :cuesheet, :songs, :track_duration, :performer, :title, :file, :genre, :date
9
+
10
+ class MalformedError < ::RuntimeError; end
11
+
12
+ PATTERNS = {
13
+ track: /TRACK (\d{1,3}) AUDIO/,
14
+ performer: /PERFORMER "(.*)"/,
15
+ title: /TITLE "(.*)"/,
16
+ index: /INDEX \d{1,3} (\d{1,3}):(\d{1,2}):(\d{1,2})/,
17
+ file: /FILE "(.*)"/,
18
+ genre: /REM GENRE (.*)\b/,
19
+ date: /REM DATE (\d*)/
20
+ }.freeze
21
+
22
+ def initialize(cuesheet, track_duration = nil)
23
+ @cuesheet = cuesheet
24
+ @track_duration = Index.new(track_duration) if track_duration
25
+ end
26
+
27
+ def parse!
28
+ @songs = parse_titles.map { |title| { title: title } }
29
+ @songs.each_with_index do |song, i|
30
+ song[:performer] = parse_performers[i] || @performer
31
+ song[:track] = parse_tracks[i]
32
+ song[:index] = parse_indices[i]
33
+ song[:file] = parse_files[i]
34
+ end
35
+ parse_genre
36
+ parse_date
37
+ raise MalformedError, "Field amounts are not all present. Cuesheet is malformed!" unless valid?
38
+
39
+ calculate_song_durations!
40
+ true
41
+ end
42
+
43
+ def position(value)
44
+ index = Index.new(value)
45
+
46
+ return @songs.first if index < @songs.first[:index]
47
+
48
+ @songs.each_with_index do |song, i|
49
+ return song if song == @songs.last
50
+ return song if between(song[:index], @songs[i + 1][:index], index)
51
+ end
52
+ end
53
+
54
+ def valid?
55
+ @songs.all? do |song|
56
+ %i[performer track index title].all? do |key|
57
+ !song[key].nil?
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def calculate_song_durations!
65
+ @songs.each_with_index do |song, i|
66
+ if song == @songs.last
67
+ song[:duration] = (@track_duration - song[:index]) if @track_duration
68
+ return nil
69
+ end
70
+ song[:duration] = @songs[i + 1][:index] - song[:index]
71
+ end
72
+ end
73
+
74
+ def between(a, b, position_index)
75
+ (position_index > a) && (position_index < b)
76
+ end
77
+
78
+ def parse_titles
79
+ unless @titles
80
+ @titles = cuesheet_scan(:title).map(&:first)
81
+ @title = @titles.delete_at(0)
82
+ end
83
+ @titles
84
+ end
85
+
86
+ def parse_performers
87
+ unless @performers
88
+ @performers = cuesheet_scan(:performer).map(&:first)
89
+ @performer = @performers.delete_at(0)
90
+ end
91
+ @performers
92
+ end
93
+
94
+ def parse_tracks
95
+ @_parse_tracks ||= cuesheet_scan(:track).map { |track| track.first.to_i }
96
+ end
97
+
98
+ def parse_indices
99
+ @_parse_indices ||= cuesheet_scan(:index).map do |index|
100
+ Index.new([index[0].to_i, index[1].to_i, index[2].to_i])
101
+ end
102
+ end
103
+
104
+ def parse_files
105
+ unless @files
106
+ @files = cuesheet_scan(:file).map(&:first)
107
+ @file = @files.delete_at(0) if @files.size == 1
108
+ end
109
+ @files
110
+ end
111
+
112
+ def parse_genre
113
+ @cuesheet.scan(PATTERNS[:genre]) do |genre|
114
+ @genre = genre.first
115
+ break
116
+ end
117
+ end
118
+
119
+ def parse_date
120
+ @cuesheet.scan(PATTERNS[:date]) do |date|
121
+ @date = date.first.to_i
122
+ break
123
+ end
124
+ end
125
+
126
+ def cuesheet_scan(field)
127
+ scan = @cuesheet.scan(PATTERNS[field])
128
+ raise MalformedError, "No fields were found for #{field}" if scan.empty?
129
+
130
+ scan
131
+ end
132
+ end
133
+ end
134
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CueBreaker
2
4
  module Dependencies
3
5
  extend self
@@ -9,9 +11,9 @@ module CueBreaker
9
11
  private
10
12
 
11
13
  def check_ffmpeg_installed!
12
- unless system('which ffmpeg > /dev/null 2>&1')
13
- raise 'ffmpeg is not installed. Please install it to use this gem.'
14
- end
14
+ return if system("which ffmpeg > /dev/null 2>&1")
15
+
16
+ raise "ffmpeg is not installed. Please install it to use this gem."
15
17
  end
16
18
  end
17
19
  end
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CueBreaker
2
4
  class FFmpegArgs
3
5
  def initialize(file, album, track)
4
- @file, @album, @track = file, album, track
6
+ @file = file
7
+ @album = album
8
+ @track = track
5
9
  end
6
10
 
7
11
  def options(output_path)
@@ -36,7 +40,7 @@ module CueBreaker
36
40
  "-metadata", "genre=#{@album.genre}",
37
41
  "-metadata", "album=#{@album.title}",
38
42
  "-metadata", "date=#{@album.date}",
39
- "-metadata", "track=#{@album.track_number(@track)}",
43
+ "-metadata", "track=#{@album.track_number(@track)}"
40
44
  ]
41
45
  end
42
46
 
@@ -1,11 +1,16 @@
1
- require 'delegate'
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
2
4
 
3
5
  module CueBreaker
4
6
  class Track
5
7
  attr_reader :title, :number, :start, :finish
6
8
 
7
9
  def initialize(title:, number:, start:, finish:)
8
- @title, @number, @start, @finish = title, number, start, finish
10
+ @title = title
11
+ @number = number
12
+ @start = start
13
+ @finish = finish
9
14
  end
10
15
 
11
16
  def present
@@ -24,7 +29,7 @@ module CueBreaker
24
29
  private
25
30
 
26
31
  def parse_time(time_str)
27
- parts = time_str.split(':').map(&:to_f)
32
+ parts = time_str.split(":").map(&:to_f)
28
33
  (parts[0] * 60) + parts[1] + (parts[2] / 75.0)
29
34
  end
30
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CueBreaker
2
- VERSION = '0.1.0'
4
+ VERSION = "0.2.0"
3
5
  end
data/lib/cue_breaker.rb CHANGED
@@ -1,6 +1,4 @@
1
- require 'rubycue'
2
- require 'fileutils'
3
- require 'open3'
1
+ # frozen_string_literal: true
4
2
 
5
- require 'cue_breaker/cli'
6
- require 'cue_breaker/core'
3
+ require "cue_breaker/cli"
4
+ require "cue_breaker/core"
metadata CHANGED
@@ -1,44 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cue_breaker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lukas Alexander
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-23 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: pry-nav
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- description: A simple hello world gem
11
+ date: 2024-05-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Breaks down large CUE/WAV files into mp3 chunks
42
14
  email: the.lukelex@gmail.com
43
15
  executables:
44
16
  - cue_break
@@ -51,23 +23,26 @@ files:
51
23
  - lib/cue_breaker/album.rb
52
24
  - lib/cue_breaker/cli.rb
53
25
  - lib/cue_breaker/core.rb
26
+ - lib/cue_breaker/cue/index.rb
27
+ - lib/cue_breaker/cue/sheet.rb
54
28
  - lib/cue_breaker/dependencies.rb
55
29
  - lib/cue_breaker/ffmpeg_args.rb
56
30
  - lib/cue_breaker/track.rb
57
31
  - lib/cue_breaker/version.rb
58
- homepage: https://rubygems.org/gems/cue_breaker
32
+ homepage: https://github.com/lukelex/cue_breaker
59
33
  licenses:
60
34
  - MIT
61
- metadata: {}
35
+ metadata:
36
+ rubygems_mfa_required: 'true'
62
37
  post_install_message:
63
38
  rdoc_options: []
64
39
  require_paths:
65
40
  - lib
66
41
  required_ruby_version: !ruby/object:Gem::Requirement
67
42
  requirements:
68
- - - ">="
43
+ - - "~>"
69
44
  - !ruby/object:Gem::Version
70
- version: '0'
45
+ version: '3.0'
71
46
  required_rubygems_version: !ruby/object:Gem::Requirement
72
47
  requirements:
73
48
  - - ">="