musictree 0.1.0 → 0.2.1

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: 6b624b50d7d91e88873fa873b3313751f8ee3ea6988a13028b1c7caf8e5303f6
4
- data.tar.gz: 94eadc0bc92fd9d041d7470660c7268bbd8041c351b841766eae48beca160313
3
+ metadata.gz: 1c4ae2d14cb182c1f34a62b95ec57a76a38f4552248b8a44a8c30137de124d7f
4
+ data.tar.gz: 26dfaca9216f36d4b5bad2d8caa030f8b2a016432f5d53cf33121d43b13fb499
5
5
  SHA512:
6
- metadata.gz: f56d53d4367ae4ca448129b55db642c183ff9b090ad120437e7dd2241b9b47a28d54395694a55f9a44b4c91314ae9d7cd898e84c1205190991d8b080eff0a38b
7
- data.tar.gz: d7e288624ac011b23b00b02c22d7ea641c0dacec91f554665533efc8971b6cf3329f399d97247e72aadacc2efcef9346db5984d5d5320151b1dc433be9946159
6
+ metadata.gz: c7086c0e64e9b511bb2c607cd36e2baa41bd4af65cf71ab2eda933a09cabf98e00d978e261c1d8af75d226d236d066b96c744846b020ffa8a3999d778bd70513
7
+ data.tar.gz: 7f6402fb7aa26e5521d3d77fb49d6777e0842592c83f260b968e15c030aa6ecc33792a0736e6f33d2e9e341a4f32920e1bfdc3379acb6e4dc8a587df644f12e7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2025-07-21
4
+
5
+ - Expose path in all objects (silly me)
6
+
7
+ ## [0.2.0] - 2025-07-21
8
+
9
+ - Big Refactoring, Trees in trees, uniform interface
10
+ - First version to be actually somewhat usable
11
+ - Use wahwah gem for now for metadata extraction
12
+
3
13
  ## [0.1.0] - 2025-07-11
4
14
 
5
15
  - Initial release
data/Rakefile CHANGED
@@ -2,9 +2,16 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
+ require "yard"
6
+
7
+ CLEAN.include "doc"
5
8
 
6
9
  Minitest::TestTask.create
7
10
 
8
11
  require "standard/rake"
9
12
 
10
13
  task default: %i[test standard]
14
+
15
+ YARD::Rake::YardocTask.new do |t|
16
+ t.files = ["lib/**/*.rb", "-", "README.md", "LICENSE.txt", "CHANGELOG.md"]
17
+ end
@@ -0,0 +1 @@
1
+ cf193a621cd919014ffa52ae2457f09daf90c5b408a1e4da88d7d207aa1c3d7f6c2ce2602de22e4c2d1890a8ed308b888043636b575cb6de025fcc2e0016e289
@@ -0,0 +1 @@
1
+ f1701fbd7ac3641f637b5685ce118d09dec9beed2af6ef739c8793ac6a366d103c83aef737cea8e30302547eb38942785d054525ef5b70910cf17daf07af6898
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @author Jan "half/byte" Krutisch
4
+ module Musictree
5
+ # Album contains the tracks of an album. It is an Enumerable for the tracks
6
+ # An Album has a title.
7
+ class Album
8
+ include Enumerable
9
+
10
+ attr_reader :title, :path
11
+
12
+ # Scans in the music files that sit in the album path. (No subdirs)
13
+ # @param path [String] The path to the album
14
+ # @return [Album] an album that exposes the tracks as an enumerable
15
+ def self.scan(path)
16
+ preliminary_name = File.basename(File.dirname(path))
17
+ moi = new(path, preliminary_name)
18
+ moi.scan_album
19
+ end
20
+
21
+ def initialize(path, preliminary_name)
22
+ @title = preliminary_name
23
+ @path = path
24
+ @tracks = []
25
+ end
26
+
27
+ def each(&block)
28
+ @tracks.each(&block)
29
+ end
30
+
31
+ def empty?
32
+ @tracks.empty?
33
+ end
34
+
35
+ def [](idx)
36
+ @tracks[idx]
37
+ end
38
+
39
+ def to_s
40
+ @tracks.map(&:to_s)
41
+ end
42
+
43
+ def inspect
44
+ @tracks.map(&:inspect)
45
+ end
46
+
47
+ # An album is an album
48
+ def album?
49
+ true
50
+ end
51
+
52
+ def scan_album
53
+ files = Dir[File.join(@path, "*.{m3u,wav,flac,mp3,opus,aiff}")]
54
+ if files.empty?
55
+ # is it a collection?
56
+ return Tree.scan(@path)
57
+ end
58
+ @tracks = files.map do |path|
59
+ Track.new(path)
60
+ end
61
+ if @tracks.all?(&:track)
62
+ @tracks = @tracks.sort_by(&:track)
63
+ end
64
+ titles = @tracks.map(&:album).uniq
65
+ @title = titles.first
66
+ self
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Musictree
6
+ class FFProbeError < Error; end
7
+
8
+ # Let's leave this in here for now if we need this for exotic cases.
9
+ class FFProber
10
+ def initialize(path)
11
+ @path = path
12
+ probe
13
+ end
14
+
15
+ def [](key)
16
+ @metadata[key]
17
+ end
18
+
19
+ def probe
20
+ @metadata = {}
21
+ stdout, stderr, status = Open3.capture3("ffprobe #{@path} -hide_banner -show_entries 'format_tags'")
22
+ matches = stdout.match(/\[FORMAT\]\n(.*)\n\[\/FORMAT\]/m)
23
+ unless status.success?
24
+ raise FFProbeError.new("Error running ffprobe: #{stderr}")
25
+ end
26
+ if matches.nil?
27
+ raise FFProbeError.new("No FORMAT block in result: #{[stdout, stderr].join}")
28
+ end
29
+ matches[1].lines.each do |line|
30
+ line_matches = line.match(/^TAG:(?<tag>\w+)=(?<value>.*?)\n/)
31
+ if line_matches
32
+ @metadata[line_matches[:tag].downcase] = line_matches[:value]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wahwah"
4
+
5
+ # @author Jan "half/byte" Krutisch
6
+ module Musictree
7
+ # A track holds the meta information for one music file
8
+ class Track
9
+ attr_reader :title, :artist, :track, :album, :path
10
+ # Scan a music file for metadata
11
+ # (The method name is consistent to the other classes but a bit shit)
12
+ # @param path [String] path to the source music file
13
+ def self.scan(path)
14
+ moi = new(path)
15
+ moi.parse_file
16
+ moi
17
+ end
18
+
19
+ def initialize(path)
20
+ @path = path
21
+ @title = File.basename(@path) # Let's set a preliminary name
22
+ parse_file
23
+ end
24
+
25
+ def inspect
26
+ {
27
+ path: @path,
28
+ title: @title,
29
+ artist: @artist,
30
+ track: @track_no,
31
+ album: @album
32
+ }.inspect
33
+ end
34
+
35
+ def parse_file
36
+ tag = WahWah.open(@path)
37
+ @title = tag.title
38
+ @artist = tag.artist
39
+ @track = tag.track
40
+ @album = tag.album
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @author Jan "half/byte" Krutisch
4
+ module Musictree
5
+ # Tree is the base entry class for Musictree.
6
+ # Use Tree.scan(path) to scan in a full tree for a given file path
7
+ # Tree is enumerable and contains either other trees (subdirectories) or albums.
8
+ class Tree
9
+ attr_reader :title, :path
10
+ include Enumerable
11
+
12
+ # Scan in a file tree
13
+ # The Tree exposes Albums or subtrees as an Enumerable
14
+ # @param path [String] the absolute path to the file tree you want to scan
15
+ # @return [Musictree::Tree]
16
+ def self.scan(path)
17
+ moi = new(path)
18
+ moi.scan_path
19
+ moi
20
+ end
21
+
22
+ def initialize(path)
23
+ @path = path
24
+ @children = []
25
+ end
26
+
27
+ def each(&block)
28
+ @children.each(&block)
29
+ end
30
+
31
+ def empty?
32
+ @children.empty?
33
+ end
34
+
35
+ def [](idx)
36
+ @children[idx]
37
+ end
38
+
39
+ # A tree is not an album
40
+ def album?
41
+ false
42
+ end
43
+
44
+ def scan_path
45
+ @title = File.basename(@path)
46
+ Dir[File.join(@path, "/*")].each do |file_or_dir|
47
+ if File.directory?(file_or_dir)
48
+ @children << Album.scan(file_or_dir)
49
+ else
50
+ raise Error.new("Only Directories expected here")
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Musictree
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/musictree.rb CHANGED
@@ -1,107 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "musictree/version"
4
- require "pathname"
5
- require "open3"
6
-
7
3
  module Musictree
8
4
  class Error < StandardError; end
9
-
10
- class FFProbeError < Error; end
11
-
12
- class FFProber
13
- def initialize(path)
14
- @path = path
15
- probe
16
- end
17
-
18
- def [](key)
19
- @metadata[key]
20
- end
21
-
22
- def probe
23
- @metadata = {}
24
- stdout, stderr, status = Open3.capture3("ffprobe #{@path} -hide_banner -show_entries 'format_tags'")
25
- matches = stdout.match(/\[FORMAT\]\n(.*)\n\[\/FORMAT\]/m)
26
- unless status.success?
27
- raise FFProbeError.new("Error running ffprobe: #{stderr}")
28
- end
29
- if matches.nil?
30
- raise FFProbeError.new("No FORMAT block in result: #{[stdout, stderr].join}")
31
- end
32
- matches[1].lines.each do |line|
33
- line_matches = line.match(/^TAG:(?<tag>\w+)=(?<value>.*?)\n/)
34
- if line_matches
35
- @metadata[line_matches[:tag]] = line_matches[:value]
36
- end
37
- end
38
- end
39
- end
40
-
41
- class Track
42
- attr_reader :title, :artist, :track_no, :album
43
- def initialize(path)
44
- @path = path
45
- @title = File.basename(@path) # Let's set a preliminary name
46
- parse_file
47
- end
48
-
49
- def parse_file
50
- prober = FFProber.new(@path)
51
- @title = prober["title"]
52
- @artist = prober["artist"]
53
- @track_no = prober["track"].to_i
54
- @album = prober["album"]
55
- end
56
- end
57
-
58
- class Album
59
- attr_reader :title, :year, :tracks
60
- def initialize(path, preliminary_name)
61
- @title = preliminary_name
62
- @path = path
63
- @tracks = []
64
- scan_album
65
- end
66
-
67
- def scan_album
68
- files = Dir[File.join(@path, "/*.{m3u,wav,flac,mp3,opus,aiff}")]
69
- @tracks = files.map do |path|
70
- Track.new(path)
71
- end
72
- if @tracks.all?(&:track_no)
73
- @tracks.sort_by(&:track_no)
74
- end
75
- titles = @tracks.map(&:album).uniq
76
- @title = titles.first
77
- end
78
-
79
- def album?
80
- true
81
- end
82
- end
83
-
84
- class Tree
85
- def initialize(path)
86
- @path = path
87
- @tree = {}
88
- scan_path
89
- end
90
-
91
- def [](key)
92
- @tree[key]
93
- end
94
-
95
- def scan_path
96
- Dir[File.join(@path, "/*")].each do |file_or_dir|
97
- if File.directory?(file_or_dir)
98
- rel = Pathname.new(file_or_dir).relative_path_from(@path)
99
- @tree[rel.to_s] = Album.new(file_or_dir, File.basename(rel))
100
-
101
- else
102
- raise Error.new("Only Directories expected here")
103
- end
104
- end
105
- end
106
- end
107
5
  end
6
+
7
+ require_relative "musictree/version"
8
+ require_relative "musictree/album"
9
+ require_relative "musictree/track"
10
+ require_relative "musictree/tree"
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "3.4.2"
metadata CHANGED
@@ -1,14 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: musictree
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
  - Jan Krutisch
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-07-13 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-07-21 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: wahwah
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.6'
26
+ - !ruby/object:Gem::Dependency
27
+ name: yard
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.37
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.9.37
12
40
  email:
13
41
  - jan@krutisch.de
14
42
  executables: []
@@ -21,8 +49,15 @@ files:
21
49
  - LICENSE.txt
22
50
  - README.md
23
51
  - Rakefile
52
+ - checksums/musictree-0.2.0.gem.sha512
53
+ - checksums/musictree-0.2.1.gem.sha512
24
54
  - lib/musictree.rb
55
+ - lib/musictree/album.rb
56
+ - lib/musictree/ff_prober.rb
57
+ - lib/musictree/track.rb
58
+ - lib/musictree/tree.rb
25
59
  - lib/musictree/version.rb
60
+ - mise.toml
26
61
  - sig/musictree.rbs
27
62
  homepage: https://codeberg.org/bandwebpage/musictree
28
63
  licenses:
@@ -45,9 +80,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
80
  - - ">="
46
81
  - !ruby/object:Gem::Version
47
82
  version: '0'
48
- requirements:
49
- - ffprobe, tested with 7.1.1 and newer
83
+ requirements: []
50
84
  rubygems_version: 3.6.2
51
85
  specification_version: 4
52
- summary: Collect metadata on a directory tree of music files with the help of ffprobe
86
+ summary: Collect metadata on a directory tree of music files
53
87
  test_files: []