nametag 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: