nametag 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.
@@ -0,0 +1 @@
1
+ --markup markdown
@@ -0,0 +1,112 @@
1
+ []: {{{1
2
+
3
+ File : README.md
4
+ Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ Date : 2013-09-06
6
+
7
+ Copyright : Copyright (C) 2013 Felix C. Stegerman
8
+ Version : v0.2.0
9
+
10
+ []: }}}1
11
+
12
+ [![Gem Version](https://badge.fury.io/rb/nametag.png)](http://badge.fury.io/rb/nametag)
13
+
14
+ ## Description
15
+ []: {{{1
16
+
17
+ nametag - set audio file tags based on file name
18
+
19
+ nametag uses regular expressions to parse paths of audio files and
20
+ then sets the file tags based on the parsed data. This allows you
21
+ to keep paths and tags in sync by creating the tags from the paths.
22
+
23
+ Everything is configurable: the path regexes, the character
24
+ substitution, and the handling of special cases. For example, the
25
+ abum `if_then_else` by The Gathering should not have its underscores
26
+ interpreted as spaces.
27
+
28
+ []: }}}1
29
+
30
+ ## Usage
31
+ []: {{{1
32
+
33
+ $ nametag -h
34
+ nametag - set audio file tags based on file name
35
+
36
+ nametag [<option(s)>]
37
+ -c, --config-file FILE Configuration file
38
+ -v, --verbose Run verbosely
39
+ -n, --no-act Do not modify files
40
+ -h, --help Show this message
41
+ --version Show version
42
+
43
+ $ nametag -v ".../Between_the_Buried_and_Me/04-Colors_(2007)/05-Ants_of_the_Sky.mp3"
44
+ /.../Between_the_Buried_and_Me/04-Colors_(2007)/05-Ants_of_the_Sky.mp3:
45
+ artist="Between the Buried and Me" album="Colors" track="05" title="Ants of the Sky" ext="mp3" album_n="04" year="2007"
46
+
47
+ []: }}}1
48
+
49
+ ## Configuration
50
+ []: {{{1
51
+
52
+ See `nametag.rb` for the default regex and substitution.
53
+
54
+ `~/.nametagrc`:
55
+
56
+ ```ruby
57
+ NameTag.configure do |c|
58
+ c.regexes << %r{ ... }
59
+ c.tr['~'] = '_'
60
+ c.process do |info, opts, tr|
61
+ if info.artist == 'The_Gathering' && info.album == 'if_then_else'
62
+ info.map_values { |k,v| k == :album ? v : tr[v] }
63
+ end
64
+ # when nothing is returned, the default processing will be used
65
+ end
66
+ end
67
+ ```
68
+
69
+ []: }}}1
70
+
71
+ ## Installing
72
+ []: {{{1
73
+
74
+ $ sudo aptitude install libtag1-dev
75
+ $ gem install nametag
76
+
77
+ []: }}}1
78
+
79
+ ## Specs & Docs
80
+ []: {{{1
81
+
82
+ $ rake spec
83
+ $ rake docs
84
+
85
+ []: }}}1
86
+
87
+ ## TODO
88
+ []: {{{1
89
+
90
+ * test config: regexes, tr
91
+ * (use cucumber to) test tag writing/behaviour?
92
+ * more specs/docs?
93
+ * ...
94
+
95
+ []: }}}1
96
+
97
+ ## License
98
+ []: {{{1
99
+
100
+ GPLv2 [1].
101
+
102
+ []: }}}1
103
+
104
+ ## References
105
+ []: {{{1
106
+
107
+ [1] GNU General Public License, version 2
108
+ --- http://www.opensource.org/licenses/GPL-2.0
109
+
110
+ []: }}}1
111
+
112
+ []: ! ( vim: set tw=70 sw=2 sts=2 et fdm=marker : )
@@ -0,0 +1,58 @@
1
+ desc 'Run specs'
2
+ task :spec do
3
+ sh 'rspec -c'
4
+ end
5
+
6
+ desc 'Run specs verbosely'
7
+ task 'spec:verbose' do
8
+ sh 'rspec -cfd'
9
+ end
10
+
11
+ desc 'Run specs verbosely, view w/ less'
12
+ task 'spec:less' do
13
+ sh 'rspec -cfd --tty | less -R'
14
+ end
15
+
16
+ desc 'Check for warnings'
17
+ task :warn do
18
+ sh "ruby -w -I lib -r nametag -e ''"
19
+ end
20
+
21
+ desc 'Check for warnings in specs'
22
+ task 'warn:spec' do
23
+ reqs = Dir['spec/**/*.rb'].sort.map { |x| "-r ./#{x}" } * ' '
24
+ sh "ruby -w -I lib -r rspec #{reqs} -e ''"
25
+ end
26
+
27
+ desc 'Check for warnings in specs (but not void context)'
28
+ task 'warn:spec:novoid' do
29
+ sh 'rake warn:spec 2>&1 | grep -v "void context"'
30
+ end
31
+
32
+ desc 'Generate docs'
33
+ task :docs do
34
+ sh 'yardoc | cat'
35
+ end
36
+
37
+ desc 'List undocumented objects'
38
+ task 'docs:undoc' do
39
+ sh 'yard stats --list-undoc'
40
+ end
41
+
42
+ desc 'Cleanup'
43
+ task :clean do
44
+ sh 'rm -rf .yardoc/ doc/ *.gem'
45
+ end
46
+
47
+ desc 'Build SNAPSHOT gem'
48
+ task :snapshot do
49
+ v = Time.new.strftime '%Y%m%d%H%M%S'
50
+ f = 'lib/nametag/version.rb'
51
+ sh "sed -ri~ 's!(SNAPSHOT)!\\1.#{v}!' #{f}"
52
+ sh 'gem build nametag.gemspec'
53
+ end
54
+
55
+ desc 'Undo SNAPSHOT gem'
56
+ task 'snapshot:undo' do
57
+ sh 'git checkout -- lib/nametag/version.rb'
58
+ end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'nametag'
4
+ require 'optparse'
5
+
6
+ info = 'nametag - set audio file tags based on file name'
7
+ usage = 'nametag [<option(s)>]'
8
+ rc = "#{ Dir.home }/.nametagrc"
9
+ options = { cfgfile: nil, verbose: 0, noact: false }
10
+
11
+ op = OptionParser.new(usage) do |o| # {{{1
12
+ o.on('-c', '--config-file FILE', 'Configuration file') do |x|
13
+ options[:cfgfile] = x
14
+ end
15
+
16
+ o.on('-v', '--verbose', 'Run verbosely') do |x|
17
+ options[:verbose] += 1
18
+ end
19
+
20
+ o.on('-n', '--no-act', 'Do not modify files') do |x|
21
+ options[:noact] = !x
22
+ end
23
+
24
+ o.on_tail('-h', '--help', 'Show this message') do
25
+ puts info, '', o; exit
26
+ end
27
+
28
+ o.on_tail('--version', 'Show version') do
29
+ puts "nametag version #{NameTag::VERSION}"; exit
30
+ end
31
+ end # }}}1
32
+
33
+ begin
34
+ op.parse! ARGV
35
+ rescue OptionParser::ParseError => e
36
+ $stderr.puts "Error: #{e}"; exit 1
37
+ end
38
+
39
+ opts = NameTag::DEFAULTS
40
+ v = options[:verbose]
41
+ cf = options[:cfgfile] || (File.exists?(rc) ? rc : nil)
42
+
43
+ load cf if cf
44
+
45
+ ARGV.each do |f| # {{{1
46
+ fn = File.realpath f
47
+ info = NameTag.parse(fn, opts) or raise "parse failed for: #{f}"
48
+ info_ = NameTag.process info, opts
49
+
50
+ if v > 0
51
+ puts "#{fn}:"
52
+ puts(v > 1 ? " - #{info}\n + #{info_}" : " #{info_}")
53
+ puts
54
+ end
55
+
56
+ NameTag.tag fn, info_ unless options[:noact]
57
+ end # }}}1
58
+
59
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,129 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : nametag.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2013-09-06
6
+ #
7
+ # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
+ # Licence : GPLv2
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'nametag/version'
13
+ require 'taglib'
14
+
15
+ module NameTag
16
+
17
+ # default pattern; matches artist, album, track, title, ext;
18
+ # optionally: album_n, year; matches e.g.
19
+ # "Between_the_Buried_and_Me/04-Colors_(2007)/05-Ants_of_the_Sky.mp3"
20
+ RX = # {{{1
21
+ %r{ / (?<artist> [^/]* )
22
+ / (?: (?<album_n> [0-9]+ ) - )?
23
+ (?<album> [^/]*? )
24
+ (?: _\( (?<year> \d{4} ) \) )?
25
+ / (?<track> [0-9]+ ) (?: - (?<title> [^/]* ) )?
26
+ \. (?<ext> mp3 | ogg | flac )
27
+ \z }x # }}}1
28
+
29
+ # default character substitution
30
+ TR = { '_|' => ' /' }
31
+
32
+ # --
33
+
34
+ # substitute characters
35
+ TR_F = ->(o) { ->(x) {
36
+ x && o.tr.reduce(x) { |a,y;k,v| k, v = y; a.tr k, v }
37
+ } }
38
+
39
+ # tr all values
40
+ PROCESS = ->(i, o, f) { i.map { |k,v| [k, f[v]] } }
41
+
42
+ # --
43
+
44
+ # better struct
45
+ module BetterStruct # {{{1
46
+ def initialize(h = {})
47
+ h.each_pair { |k,v| self[k] = v }
48
+ end
49
+ def map(&b)
50
+ each_pair.map(&b)
51
+ end
52
+ def map_values(&b)
53
+ map { |k,v| [k,b[k,v]] }
54
+ end
55
+ end # }}}1
56
+
57
+ # options
58
+ Options = Struct.new(:regexes, :tr, :_process) do
59
+ include BetterStruct
60
+ def process(&b)
61
+ _process << b
62
+ end
63
+ end
64
+
65
+ # info
66
+ Info = Struct.new(:artist, :album, :track, :title, # {{{1
67
+ :ext, :album_n, :year) do
68
+ include BetterStruct
69
+ def to_s
70
+ map { |k,v| "#{k}=#{v.inspect}" } *' '
71
+ end
72
+ end # }}}1
73
+
74
+ # default options
75
+ DEFAULTS = Options.new regexes: [RX], tr: TR, _process: []
76
+
77
+ # --
78
+
79
+ # configure nametag by changing DEFAULTS, which is passed to the
80
+ # block
81
+ def self.configure(c = DEFAULTS, &b)
82
+ b[c]; c
83
+ end
84
+
85
+ # --
86
+
87
+ # parse filename; tries each regex in turn
88
+ # @return [Info, nil]
89
+ def self.parse(filename, opts)
90
+ m = _first_map(opts.regexes) { |rx| rx.match filename }
91
+ m ? Info.new(Hash[m.names.map { |x| [x, m[x]] }]) : nil
92
+ end
93
+
94
+ # process info object; tries each processing function in turn
95
+ # @return [Info]
96
+ def self.process(info, opts) # {{{1
97
+ ps = opts._process + [PROCESS]; tr = TR_F[opts]
98
+ info_ = _first_map(ps) { |p| p[info, opts, tr] }
99
+ case info_
100
+ when Info ; info_
101
+ when Hash ; Info.new(info_)
102
+ when Array; Info.new(Hash[info_])
103
+ else raise 'processing function dit not return Info|Hash|Array'
104
+ end
105
+ end # }}}1
106
+
107
+ # process file: set tags from info, save file
108
+ def self.tag(filename, info) # {{{1
109
+ TagLib::FileRef.open(filename) do |file|
110
+ tag = file.tag
111
+ tag.artist = info[:artist]
112
+ tag.album = info[:album]
113
+ tag.track = info[:track].to_i
114
+ tag.title = info[:title] || "[track #{info[:track]}]"
115
+ tag.year = info[:year].to_i
116
+ file.save
117
+ end
118
+ end # }}}1
119
+
120
+ # --
121
+
122
+ # lazy xs.map(&f).first
123
+ def self._first_map(xs, &f)
124
+ xs.each { |x| y = f[x]; return y if y }; nil
125
+ end
126
+
127
+ end
128
+
129
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
@@ -0,0 +1,4 @@
1
+ module NameTag
2
+ VERSION = '0.2.0'
3
+ DATE = '2013-09-05'
4
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../lib/nametag/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'nametag'
5
+ s.homepage = 'https://github.com/obfusk/nametag'
6
+ s.summary = 'set audio file tags based on file name'
7
+
8
+ s.description = <<-END.gsub(/^ {4}/, '')
9
+ set audio file tags based on file name
10
+
11
+ ...
12
+ END
13
+
14
+ s.version = NameTag::VERSION
15
+ s.date = NameTag::DATE
16
+
17
+ s.authors = [ 'Felix C. Stegerman' ]
18
+ s.email = %w{ flx@obfusk.net }
19
+
20
+ s.licenses = %w{ GPLv2 }
21
+
22
+ s.executables = %w{ nametag }
23
+ s.files = %w{ .yardopts README.md Rakefile bin/nametag } \
24
+ + %w{ nametag.gemspec } \
25
+ + Dir['{lib,spec}/**/*.rb']
26
+
27
+ s.add_runtime_dependency 'taglib-ruby'
28
+
29
+ s.add_development_dependency 'rake'
30
+ s.add_development_dependency 'rspec'
31
+
32
+ s.required_ruby_version = '>= 1.9.1'
33
+ end
@@ -0,0 +1,102 @@
1
+ # -- ; {{{1
2
+ #
3
+ # File : nametag_spec.rb
4
+ # Maintainer : Felix C. Stegerman <flx@obfusk.net>
5
+ # Date : 2013-09-06
6
+ #
7
+ # Copyright : Copyright (C) 2013 Felix C. Stegerman
8
+ # Licence : GPLv2
9
+ #
10
+ # -- ; }}}1
11
+
12
+ require 'nametag'
13
+
14
+ nt = NameTag
15
+
16
+ describe nt do
17
+
18
+ ants = { # {{{1
19
+ file: '.../Between_the_Buried_and_Me/04-Colors_(2007)/' +
20
+ '05-Ants_of_the_Sky.mp3',
21
+ info: nt::Info.new(artist: 'Between_the_Buried_and_Me',
22
+ album: 'Colors', track: '05', title: 'Ants_of_the_Sky',
23
+ ext: 'mp3', album_n: '04', year: '2007'),
24
+ info_: nt::Info.new(artist: 'Between the Buried and Me',
25
+ album: 'Colors', track: '05', title: 'Ants of the Sky',
26
+ ext: 'mp3', album_n: '04', year: '2007')
27
+ } # }}}1
28
+
29
+ harl = { # {{{1
30
+ file: '.../Opeth/08-Ghost_Reveries_(2005)/' +
31
+ '05-Reverie|Harlequin_Forest.mp3',
32
+ info: nt::Info.new(artist: 'Opeth',
33
+ album: 'Ghost_Reveries', track: '05',
34
+ title: 'Reverie|Harlequin_Forest',
35
+ ext: 'mp3', album_n: '08', year: '2005'),
36
+ info_: nt::Info.new(artist: 'Opeth',
37
+ album: 'Ghost Reveries', track: '05',
38
+ title: 'Reverie/Harlequin Forest',
39
+ ext: 'mp3', album_n: '08', year: '2005')
40
+ } # }}}1
41
+
42
+ roll = { # {{{1
43
+ file: '.../The_Gathering/03-if_then_else_(2000)/' +
44
+ '01-Rollercoaster.mp3',
45
+ info: nt::Info.new(artist: 'The_Gathering',
46
+ album: 'if_then_else', track: '01',
47
+ title: 'Rollercoaster', ext: 'mp3',
48
+ album_n: '03', year: '2000'),
49
+ info_: nt::Info.new(artist: 'The Gathering',
50
+ album: 'if_then_else', track: '01',
51
+ title: 'Rollercoaster', ext: 'mp3',
52
+ album_n: '03', year: '2000')
53
+ } # }}}1
54
+
55
+ opts = nt.configure(nt::DEFAULTS.dup) do |c| # {{{1
56
+ c.process do |info, opts, tr|
57
+ if info.artist == 'The_Gathering' &&
58
+ info.album == 'if_then_else'
59
+ info.map_values { |k,v| k == :album ? v : tr[v] }
60
+ end
61
+ end
62
+ end # }}}1
63
+
64
+ context 'RX' do # {{{1
65
+ it 'matches ants' do
66
+ expect(nt::RX).to match ants[:file]
67
+ end
68
+ it 'matches harl' do
69
+ expect(nt::RX).to match harl[:file]
70
+ end
71
+ it 'matches roll' do
72
+ expect(nt::RX).to match roll[:file]
73
+ end
74
+ end # }}}1
75
+
76
+ context 'parse' do # {{{1
77
+ it 'matches ants' do
78
+ expect(nt.parse(ants[:file], opts)).to eq ants[:info]
79
+ end
80
+ it 'matches harl' do
81
+ expect(nt.parse(harl[:file], opts)).to eq harl[:info]
82
+ end
83
+ it 'matches roll' do
84
+ expect(nt.parse(roll[:file], opts)).to eq roll[:info]
85
+ end
86
+ end # }}}1
87
+
88
+ context 'process' do # {{{1
89
+ it 'transforms ants correctly' do
90
+ expect(nt.process(ants[:info], opts)).to eq ants[:info_]
91
+ end
92
+ it 'transforms harl correctly' do
93
+ expect(nt.process(harl[:info], opts)).to eq harl[:info_]
94
+ end
95
+ it 'transforms roll correctly' do
96
+ expect(nt.process(roll[:info], opts)).to eq roll[:info_]
97
+ end
98
+ end # }}}1
99
+
100
+ end
101
+
102
+ # vim: set tw=70 sw=2 sts=2 et fdm=marker :
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nametag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Felix C. Stegerman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: taglib-ruby
16
+ requirement: &18703080 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *18703080
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &16200320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *16200320
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &16197100 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *16197100
47
+ description: ! 'set audio file tags based on file name
48
+
49
+
50
+ ...
51
+
52
+ '
53
+ email:
54
+ - flx@obfusk.net
55
+ executables:
56
+ - nametag
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - .yardopts
61
+ - README.md
62
+ - Rakefile
63
+ - bin/nametag
64
+ - nametag.gemspec
65
+ - lib/nametag.rb
66
+ - lib/nametag/version.rb
67
+ - spec/nametag_spec.rb
68
+ homepage: https://github.com/obfusk/nametag
69
+ licenses:
70
+ - GPLv2
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: 1.9.1
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.11
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: set audio file tags based on file name
93
+ test_files: []
94
+ has_rdoc: