typo 4.0.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/app/controllers/admin/comments_controller.rb +1 -1
  2. data/app/controllers/admin/content_controller.rb +1 -3
  3. data/app/controllers/admin/feedback_controller.rb +36 -31
  4. data/app/controllers/admin/sidebar_controller.rb +13 -2
  5. data/app/controllers/admin/users_controller.rb +2 -1
  6. data/app/controllers/articles_controller.rb +10 -19
  7. data/app/controllers/xml_controller.rb +2 -2
  8. data/app/helpers/admin/base_helper.rb +7 -3
  9. data/app/helpers/application_helper.rb +2 -2
  10. data/app/helpers/articles_helper.rb +5 -4
  11. data/app/models/article.rb +16 -10
  12. data/app/models/blog.rb +4 -10
  13. data/app/models/comment.rb +17 -36
  14. data/app/models/content.rb +31 -53
  15. data/app/models/content_state/base.rb +46 -3
  16. data/app/models/content_state/draft.rb +2 -9
  17. data/app/models/content_state/ham.rb +31 -0
  18. data/app/models/content_state/just_marked_as_ham.rb +10 -0
  19. data/app/models/content_state/just_marked_as_spam.rb +23 -0
  20. data/app/models/content_state/just_presumed_ham.rb +37 -0
  21. data/app/models/content_state/just_published.rb +15 -8
  22. data/app/models/content_state/new.rb +3 -10
  23. data/app/models/content_state/presumed_ham.rb +27 -0
  24. data/app/models/content_state/presumed_spam.rb +31 -0
  25. data/app/models/content_state/publication_pending.rb +7 -9
  26. data/app/models/content_state/published.rb +10 -9
  27. data/app/models/content_state/spam.rb +23 -0
  28. data/app/models/content_state/unclassified.rb +29 -0
  29. data/app/models/content_state/withdrawn.rb +28 -0
  30. data/app/models/email_notifier.rb +0 -1
  31. data/app/models/feedback.rb +151 -0
  32. data/app/models/trackback.rb +22 -29
  33. data/app/views/admin/feedback/_item.rhtml +5 -5
  34. data/app/views/admin/feedback/list.rhtml +13 -11
  35. data/app/views/admin/general/index.rhtml +8 -4
  36. data/app/views/admin/users/show.rhtml +7 -1
  37. data/app/views/articles/read.rhtml +2 -2
  38. data/bin/typo +7 -6
  39. data/components/plugins/sidebars/recent_comments_controller.rb +1 -1
  40. data/config/environment.rb +2 -0
  41. data/db/migrate/046_fixup_forthcoming_publications.rb +1 -1
  42. data/db/migrate/048_remove_count_caching.rb +31 -0
  43. data/db/migrate/049_move_feedback_to_new_state_machine.rb +33 -0
  44. data/db/schema.mysql-v3.sql +3 -4
  45. data/db/schema.mysql.sql +3 -4
  46. data/db/schema.postgresql.sql +3 -4
  47. data/db/schema.rb +12 -17
  48. data/db/schema.sqlite.sql +3 -4
  49. data/db/schema.sqlserver.sql +3 -4
  50. data/db/schema_version +1 -1
  51. data/doc/Installer.txt +4 -0
  52. data/lib/jabber_notify.rb +6 -2
  53. data/lib/sidebars/plugin.rb +2 -1
  54. data/lib/tasks/release.rake +5 -4
  55. data/lib/typo_version.rb +1 -1
  56. data/public/stylesheets/administration.css +22 -2
  57. data/test/fixtures/blogs.yml +2 -0
  58. data/test/fixtures/contents.yml +7 -7
  59. data/test/functional/admin/users_controller_test.rb +3 -0
  60. data/test/functional/articles_controller_test.rb +16 -1
  61. data/test/mocks/test/xmlrpc_mock.rb +5 -4
  62. data/test/unit/article_test.rb +16 -4
  63. data/test/unit/comment_test.rb +57 -35
  64. data/test/unit/content_state/factory_test.rb +7 -6
  65. data/test/unit/ping_test.rb +14 -0
  66. data/test/unit/trackback_test.rb +16 -15
  67. metadata +26 -26
  68. data/config/database.yml-pgsql +0 -17
  69. data/config/database.yml.sqlite +0 -14
  70. data/config/mail.yml +0 -8
  71. data/config/mongrel.conf +0 -2
  72. data/db/converters/mt-import.rb +0 -72
  73. data/db/development_structure.sql +0 -691
  74. data/installer/rails-installer.rb +0 -527
  75. data/installer/rails-installer/commands.rb +0 -118
  76. data/installer/rails-installer/web-servers.rb +0 -110
  77. data/log/development.log-1 +0 -991
  78. data/log/development.log-2 +0 -422
  79. data/log/development.log-3 +0 -429
  80. data/log/development.log-4 +0 -174
  81. data/svk-commitP6cVv.tmp +0 -1
  82. data/vendor/ruby-mp3info/lib/mp3info.rb +0 -720
@@ -1,174 +0,0 @@
1
-
2
-
3
- Processing ArticlesController#index (for 127.0.0.1 at 2006-07-08 00:17:23) [GET]
4
- Parameters: {"action"=>"index", "controller"=>"articles"}
5
- Blog Load (0.001571) SELECT * FROM blogs ORDER BY id LIMIT 1
6
- Trigger Load (0.001660) SELECT * FROM triggers WHERE (due_at <= '2006-07-08 00:17:23') 
7
- SQL (0.005065)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
8
- FROM pg_attribute a LEFT JOIN pg_attrdef d
9
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
10
- WHERE a.attrelid = 'users'::regclass
11
- AND a.attnum > 0 AND NOT a.attisdropped
12
- ORDER BY a.attnum
13
- 
14
- SQL (0.001335) SELECT count(*) AS count_all FROM users 
15
- SQL (0.003723)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
16
- FROM pg_attribute a LEFT JOIN pg_attrdef d
17
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
18
- WHERE a.attrelid = 'blogs'::regclass
19
- AND a.attnum > 0 AND NOT a.attisdropped
20
- ORDER BY a.attnum
21
- 
22
- SQL (0.005265)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
23
- FROM pg_attribute a LEFT JOIN pg_attrdef d
24
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
25
- WHERE a.attrelid = 'contents'::regclass
26
- AND a.attnum > 0 AND NOT a.attisdropped
27
- ORDER BY a.attnum
28
- 
29
- Article Count (3.383297) SELECT COUNT(DISTINCT contents.id) FROM contents LEFT OUTER JOIN articles_categories ON articles_categories.article_id = contents.id LEFT OUTER JOIN categories ON categories.id = articles_categories.category_id LEFT OUTER JOIN articles_tags ON articles_tags.article_id = contents.id LEFT OUTER JOIN tags ON tags.id = articles_tags.tag_id WHERE (published = 't' AND contents.created_at < '2006-07-08 00:17:23' AND blog_id = 1) AND ( (contents."type" = 'Article' ) ) 
30
- SQL (0.004491)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
31
- FROM pg_attribute a LEFT JOIN pg_attrdef d
32
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
33
- WHERE a.attrelid = 'categories'::regclass
34
- AND a.attnum > 0 AND NOT a.attisdropped
35
- ORDER BY a.attnum
36
- 
37
- SQL (0.005264)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
38
- FROM pg_attribute a LEFT JOIN pg_attrdef d
39
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
40
- WHERE a.attrelid = 'tags'::regclass
41
- AND a.attnum > 0 AND NOT a.attisdropped
42
- ORDER BY a.attnum
43
- 
44
- Article Load IDs For Limited Eager Loading (0.016903) SELECT id FROM contents WHERE (published = 't' AND contents.created_at < '2006-07-08 00:17:23' AND blog_id = 1) AND ( (contents."type" = 'Article' ) ) ORDER BY contents.published_at DESC LIMIT 15 OFFSET 0
45
- Article Load Including Associations (0.150279) SELECT contents."id" AS t0_r0, contents."title" AS t0_r1, contents."author" AS t0_r2, contents."body" AS t0_r3, contents."body_html" AS t0_r4, contents."extended" AS t0_r5, contents."excerpt" AS t0_r6, contents."keywords" AS t0_r7, contents."created_at" AS t0_r8, contents."updated_at" AS t0_r9, contents."extended_html" AS t0_r10, contents."user_id" AS t0_r11, contents."permalink" AS t0_r12, contents."guid" AS t0_r13, contents."text_filter_id" AS t0_r14, contents."whiteboard" AS t0_r15, contents."comments_count" AS t0_r16, contents."trackbacks_count" AS t0_r17, contents."type" AS t0_r18, contents."article_id" AS t0_r19, contents."email" AS t0_r20, contents."url" AS t0_r21, contents."ip" AS t0_r22, contents."blog_name" AS t0_r23, contents."name" AS t0_r24, contents."published" AS t0_r25, contents."allow_pings" AS t0_r26, contents."allow_comments" AS t0_r27, contents."blog_id" AS t0_r28, contents."published_at" AS t0_r29, categories."id" AS t1_r0, categories."name" AS t1_r1, categories."position" AS t1_r2, categories."is_primary" AS t1_r3, categories."permalink" AS t1_r4, tags."id" AS t2_r0, tags."name" AS t2_r1, tags."created_at" AS t2_r2, tags."updated_at" AS t2_r3, tags."display_name" AS t2_r4 FROM contents LEFT OUTER JOIN articles_categories ON articles_categories.article_id = contents.id LEFT OUTER JOIN categories ON categories.id = articles_categories.category_id LEFT OUTER JOIN articles_tags ON articles_tags.article_id = contents.id LEFT OUTER JOIN tags ON tags.id = articles_tags.tag_id WHERE (published = 't' AND contents.created_at < '2006-07-08 00:17:23' AND blog_id = 1) AND ( (contents."type" = 'Article' ) ) AND contents.id IN ('1640', '562', '561', '560', '559', '558', '557', '556', '555', '554', '553', '552', '551', '550', '549') ORDER BY contents.published_at DESC 
46
- Rendering within ../../themes/scribbish/layouts/default
47
- Rendering articles/index
48
- Blog Load (0.002095) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
49
- User Load (0.002659) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
50
- Blog Load (0.001452) SELECT * FROM blogs ORDER BY id LIMIT 1
51
- Rendered articles/_article (0.07813)
52
- Blog Load (0.001730) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
53
- User Load (0.002122) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
54
- Rendered articles/_article (0.01851)
55
- Blog Load (0.002361) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
56
- User Load (0.001859) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
57
- Rendered articles/_article (0.01787)
58
- Blog Load (0.002031) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
59
- User Load (0.001887) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
60
- Rendered articles/_article (0.13746)
61
- Blog Load (0.001752) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
62
- User Load (0.002257) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
63
- Rendered articles/_article (0.01825)
64
- Blog Load (0.001933) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
65
- User Load (0.155534) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
66
- Rendered articles/_article (0.26178)
67
- Blog Load (0.001735) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
68
- User Load (0.002489) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
69
- Rendered articles/_article (0.01920)
70
- Blog Load (0.002814) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
71
- User Load (0.001888) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
72
- Rendered articles/_article (0.13118)
73
- Blog Load (0.001763) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
74
- User Load (0.001945) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
75
- Rendered articles/_article (0.01881)
76
- Blog Load (0.002165) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
77
- User Load (0.001878) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
78
- Rendered articles/_article (0.01625)
79
- Blog Load (0.003068) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
80
- User Load (0.001868) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
81
- Rendered articles/_article (0.01773)
82
- Blog Load (0.001745) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
83
- User Load (0.002177) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
84
- Rendered articles/_article (0.01745)
85
- Blog Load (0.001913) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
86
- User Load (0.001883) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
87
- Rendered articles/_article (0.12971)
88
- Blog Load (0.002062) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
89
- User Load (0.001928) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
90
- Rendered articles/_article (0.01989)
91
- Blog Load (0.001737) SELECT * FROM blogs WHERE (blogs.id = 1) LIMIT 1
92
- User Load (0.001901) SELECT * FROM users WHERE (users.id = 1) LIMIT 1
93
- Rendered articles/_article (0.01962)
94
- Rendered articles/_search (0.00467)
95
- Start rendering component ({:controller=>SidebarController, :action=>"display_plugins"}):
96
-
97
-
98
- Processing SidebarController#display_plugins (for 127.0.0.1 at 2006-07-08 00:17:28)
99
- Blog Load (0.001420) SELECT * FROM blogs ORDER BY id LIMIT 1
100
- Trigger Load (0.001471) SELECT * FROM triggers WHERE (due_at <= '2006-07-08 00:17:28') 
101
- Sidebar Load (0.005570) SELECT * FROM sidebars WHERE (active_position is not null) ORDER BY active_position 
102
- SQL (0.005188)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
103
- FROM pg_attribute a LEFT JOIN pg_attrdef d
104
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
105
- WHERE a.attrelid = 'sidebars'::regclass
106
- AND a.attnum > 0 AND NOT a.attisdropped
107
- ORDER BY a.attnum
108
- 
109
- Rendering sidebar/display_plugins
110
- Start rendering component ({:controller=>Plugins::Sidebars::TagController, :action=>"index", :layout=>false}):
111
-
112
-
113
- Processing TagController#index (for 127.0.0.1 at 2006-07-08 00:17:28)
114
- Blog Load (0.001579) SELECT * FROM blogs ORDER BY id LIMIT 1
115
- Trigger Load (0.001495) SELECT * FROM triggers WHERE (due_at <= '2006-07-08 00:17:28') 
116
- Tag Load (0.045230) 
117
- SELECT tags.id, tags.name, tags.display_name, COUNT(articles_tags.article_id) AS article_counter
118
- FROM tags tags LEFT OUTER JOIN articles_tags articles_tags
119
- ON articles_tags.tag_id = tags.id
120
- LEFT OUTER JOIN contents articles
121
- ON articles_tags.article_id = articles.id
122
- WHERE articles.published = 't'
123
- GROUP BY tags.id, tags.name, tags.display_name
124
- ORDER BY article_counter DESC
125
- LIMIT 20
126
- 
127
- Rendering plugins/sidebars/tag/content
128
- Completed in 0.18452 (5 reqs/sec) | Rendering: 0.12484 (67%) | DB: 0.04830 (26%) | 200 OK [http://localhost/]
129
-
130
-
131
- End of component rendering
132
- BENCHMARK: display_plugins: tag (0.18900)
133
- Rendered sidebar/_sidebar (0.18990)
134
- Start rendering component ({:controller=>Plugins::Sidebars::ArchivesController, :action=>"index", :layout=>false}):
135
-
136
-
137
- Processing ArchivesController#index (for 127.0.0.1 at 2006-07-08 00:17:28)
138
- Blog Load (0.001800) SELECT * FROM blogs ORDER BY id LIMIT 1
139
- Trigger Load (0.001495) SELECT * FROM triggers WHERE (due_at <= '2006-07-08 00:17:28') 
140
- Content Load (0.023723) select count(*) as count, extract(year from published_at)||' '||lpad(extract(month from published_at),2,'0') as date from contents where type='Article' and published = 't' and published_at < '2006-07-08 00:17:28' group by date order by date desc limit '10'
141
- SQL (0.004369)  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
142
- FROM pg_attribute a LEFT JOIN pg_attrdef d
143
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
144
- WHERE a.attrelid = 'contents'::regclass
145
- AND a.attnum > 0 AND NOT a.attisdropped
146
- ORDER BY a.attnum
147
- 
148
- Rendering plugins/sidebars/archives/content
149
- Completed in 0.18893 (5 reqs/sec) | Rendering: 0.00836 (4%) | DB: 0.03139 (16%) | 200 OK [http://localhost/]
150
-
151
-
152
- End of component rendering
153
- BENCHMARK: display_plugins: archives (0.19216)
154
- Rendered sidebar/_sidebar (0.19309)
155
- Start rendering component ({:controller=>Plugins::Sidebars::StaticController, :action=>"index", :layout=>false}):
156
-
157
-
158
- Processing StaticController#index (for 127.0.0.1 at 2006-07-08 00:17:28)
159
- Blog Load (0.001424) SELECT * FROM blogs ORDER BY id LIMIT 1
160
- Trigger Load (0.002276) SELECT * FROM triggers WHERE (due_at <= '2006-07-08 00:17:28') 
161
- Rendering plugins/sidebars/static/content
162
- Completed in 0.13924 (7 reqs/sec) | Rendering: 0.00240 (1%) | DB: 0.00370 (2%) | 200 OK [http://localhost/]
163
-
164
-
165
- End of component rendering
166
- BENCHMARK: display_plugins: static (0.14366)
167
- Rendered sidebar/_sidebar (0.14466)
168
- BENCHMARK: display_plugins (0.53017)
169
- Completed in 0.56401 (1 reqs/sec) | Rendering: 0.53488 (94%) | DB: 0.23028 (40%) | 200 OK [http://localhost/]
170
-
171
-
172
- End of component rendering
173
- Article Load (0.011033) SELECT * FROM contents WHERE (published = 't' AND created_at > '2006-07-08 00:17:23') AND ( (contents."type" = 'Article' ) ) ORDER BY created_at ASC LIMIT 1
174
- Completed in 5.79087 (0 reqs/sec) | Rendering: 1.89160 (32%) | DB: 3.58989 (61%) | 200 OK [http://localhost/]
@@ -1 +0,0 @@
1
- Tagging 3.99.4
@@ -1,720 +0,0 @@
1
- # $Id: mp3info.rb,v 1.5 2005/04/26 13:41:41 moumar Exp $
2
- # = Description
3
- #
4
- # ruby-mp3info gives you access to low level informations on mp3 files
5
- # (bitrate, length, samplerate, etc...). It can read, write, remove id3v1 tag
6
- # and read id3v2. It is written in pure ruby.
7
- #
8
- #
9
- # = Download
10
- #
11
- # get tar.gz at
12
- # http://rubyforge.org/projects/ruby-mp3info/
13
- #
14
- #
15
- # = Installation
16
- #
17
- # $ ruby install.rb config
18
- # $ ruby install.rb setup
19
- # # ruby install.rb install
20
- #
21
- # or
22
- #
23
- # # gem install ruby-mp3info
24
- #
25
- #
26
- # = Example
27
- #
28
- # require "mp3info"
29
- #
30
- # mp3info = Mp3Info.new("myfile.mp3")
31
- # puts mp3info
32
- #
33
- #
34
- # = Testing
35
- #
36
- # Test::Unit library is used for tests. see http://testunit.talbott.ws/
37
- #
38
- # $ ruby test.rb
39
- #
40
- #
41
- # = ToDo
42
- #
43
- # * adding write support for ID3v2 tags
44
- # * adding a test for id3v2
45
- # * encoder detection
46
- #
47
- #
48
- # = Changelog
49
- #
50
- # [0.4 26/04/2005]
51
- #
52
- # * fixes in vbr mode
53
- # * removed extract_info_from_head() function
54
- # * now try several times to find a good header frame before giving up
55
- # * correct handling of unicode in v2 tags. Require standard "iconv" library if such tags are used
56
- # * FIXED if a tag appears more than one time, create an array with every value found for this tag
57
- #
58
- #
59
- # [0.3 04/05/2004]
60
- #
61
- # * massive changes of most of the code to make it easier to read & hopefully run faster
62
- # * ID2TAGS hash is just informative now, no use of it in the code. id3v2 tag fields are read in directly
63
- # * added support for id3 v2.2 and v2.4 (0.2.1 only supported v2.3)
64
- # * much improved vbr duration guessing
65
- # * made Mp3Info#to_s output to be prettier
66
- # * moved hastag1? and hastag2? to be class booleans instead of functions (now named hastag1 and hastag2)
67
- # * fixed a bug on computing "error_protection" attribute
68
- # * new attribute "tag", which is a sort of "universal" tag, regardless of the tag version, 1 or 2, with the same keys as @tag1
69
- # * new method hastag?, which test the presence of any tag
70
- #
71
- #
72
- # [0.2.1 04/09/2003]
73
- #
74
- # * filename attribute added
75
- # * mp3 files are opened read-only now [Alan Davies <alan__DOT_davies__AT__thomson.com>]
76
- # * Mp3Info#initialize: bugfixes [Alan Davies <alan__DOT_davies__AT__thomson.com>]
77
- # * put NULLs in year field in id3v1 tags instead of zeros [Alan Davies <alan__DOT_davies__AT__thomson.com>]
78
- # * Mp3Info#gettag1: remove null at end of strings [Alan Davies <alan__DOT_davies__AT__thomson.com>]
79
- # * Mp3Info#extract_infos_from_head(): some brackets missed [Alan Davies <alan__DOT_davies__AT__thomson.com>]
80
- #
81
- #
82
- # [0.2 18/08/2003]
83
- #
84
- # * writing, reading and removing of id3v1 tags
85
- # * reading of id3v2 tags
86
- # * test suite improved
87
- # * to_s method added
88
- # * length attribute is a Float now
89
- #
90
- #
91
- # [0.1 17/03/2003]
92
- #
93
- # * Initial version
94
- #
95
- #
96
- # License:: Ruby
97
- # Author:: Guillaume Pierronnet (mailto:moumar_AT__rubyforge_DOT_org)
98
- # Website:: http://ruby-mp3info.rubyforge.org/
99
-
100
- # Raised on any kind of error related to ruby-mp3info
101
- class Mp3InfoError < StandardError ; end
102
-
103
- class Mp3InfoInternalError < StandardError #:nodoc:
104
- end
105
-
106
- class Numeric
107
- ### returns the selected bit range (b, a) as a number
108
- ### NOTE: b > a if not, returns 0
109
- def bits(b, a)
110
- t = 0
111
- b.downto(a) { |i| t += t + self[i] }
112
- t
113
- end
114
- end
115
-
116
- class Hash
117
- ### lets you specify hash["key"] as hash.key
118
- ### this came from CodingInRuby on RubyGarden
119
- ### http://www.rubygarden.org/ruby?CodingInRuby
120
- def method_missing(meth,*args)
121
- if /=$/=~(meth=meth.id2name) then
122
- self[meth[0...-1]] = (args.length<2 ? args[0] : args)
123
- else
124
- self[meth]
125
- end
126
- end
127
- end
128
-
129
- class File
130
- def get32bits
131
- (getc << 24) + (getc << 16) + (getc << 8) + getc
132
- end
133
- def get_syncsafe
134
- (getc << 21) + (getc << 14) + (getc << 7) + getc
135
- end
136
- end
137
-
138
- class Mp3Info
139
-
140
- VERSION = "0.4"
141
-
142
- LAYER = [ nil, 3, 2, 1]
143
- BITRATE = [
144
- [
145
- [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
146
- [32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
147
- [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] ],
148
- [
149
- [32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
150
- [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
151
- [8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
152
- ]
153
- ]
154
- SAMPLERATE = [
155
- [ 44100, 48000, 32000 ],
156
- [ 22050, 24000, 16000 ]
157
- ]
158
- CHANNEL_MODE = [ "Stereo", "JStereo", "Dual Channel", "Single Channel"]
159
-
160
- GENRES = [
161
- "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk",
162
- "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies",
163
- "Other", "Pop", "R&B", "Rap", "Reggae", "Rock",
164
- "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks",
165
- "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk",
166
- "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House",
167
- "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass",
168
- "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
169
- "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
170
- "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
171
- "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
172
- "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
173
- "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical",
174
- "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing",
175
- "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
176
- "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
177
- "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
178
- "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
179
- "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
180
- "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet",
181
- "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
182
- "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror",
183
- "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat",
184
- "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C",
185
- "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
186
- "SynthPop" ]
187
-
188
-
189
- ID2TAGS = {
190
- "AENC" => "Audio encryption",
191
- "APIC" => "Attached picture",
192
- "COMM" => "Comments",
193
- "COMR" => "Commercial frame",
194
- "ENCR" => "Encryption method registration",
195
- "EQUA" => "Equalization",
196
- "ETCO" => "Event timing codes",
197
- "GEOB" => "General encapsulated object",
198
- "GRID" => "Group identification registration",
199
- "IPLS" => "Involved people list",
200
- "LINK" => "Linked information",
201
- "MCDI" => "Music CD identifier",
202
- "MLLT" => "MPEG location lookup table",
203
- "OWNE" => "Ownership frame",
204
- "PRIV" => "Private frame",
205
- "PCNT" => "Play counter",
206
- "POPM" => "Popularimeter",
207
- "POSS" => "Position synchronisation frame",
208
- "RBUF" => "Recommended buffer size",
209
- "RVAD" => "Relative volume adjustment",
210
- "RVRB" => "Reverb",
211
- "SYLT" => "Synchronized lyric/text",
212
- "SYTC" => "Synchronized tempo codes",
213
- "TALB" => "Album/Movie/Show title",
214
- "TBPM" => "BPM (beats per minute)",
215
- "TCOM" => "Composer",
216
- "TCON" => "Content type",
217
- "TCOP" => "Copyright message",
218
- "TDAT" => "Date",
219
- "TDLY" => "Playlist delay",
220
- "TENC" => "Encoded by",
221
- "TEXT" => "Lyricist/Text writer",
222
- "TFLT" => "File type",
223
- "TIME" => "Time",
224
- "TIT1" => "Content group description",
225
- "TIT2" => "Title/songname/content description",
226
- "TIT3" => "Subtitle/Description refinement",
227
- "TKEY" => "Initial key",
228
- "TLAN" => "Language(s)",
229
- "TLEN" => "Length",
230
- "TMED" => "Media type",
231
- "TOAL" => "Original album/movie/show title",
232
- "TOFN" => "Original filename",
233
- "TOLY" => "Original lyricist(s)/text writer(s)",
234
- "TOPE" => "Original artist(s)/performer(s)",
235
- "TORY" => "Original release year",
236
- "TOWN" => "File owner/licensee",
237
- "TPE1" => "Lead performer(s)/Soloist(s)",
238
- "TPE2" => "Band/orchestra/accompaniment",
239
- "TPE3" => "Conductor/performer refinement",
240
- "TPE4" => "Interpreted, remixed, or otherwise modified by",
241
- "TPOS" => "Part of a set",
242
- "TPUB" => "Publisher",
243
- "TRCK" => "Track number/Position in set",
244
- "TRDA" => "Recording dates",
245
- "TRSN" => "Internet radio station name",
246
- "TRSO" => "Internet radio station owner",
247
- "TSIZ" => "Size",
248
- "TSRC" => "ISRC (international standard recording code)",
249
- "TSSE" => "Software/Hardware and settings used for encoding",
250
- "TYER" => "Year",
251
- "TXXX" => "User defined text information frame",
252
- "UFID" => "Unique file identifier",
253
- "USER" => "Terms of use",
254
- "USLT" => "Unsychronized lyric/text transcription",
255
- "WCOM" => "Commercial information",
256
- "WCOP" => "Copyright/Legal information",
257
- "WOAF" => "Official audio file webpage",
258
- "WOAR" => "Official artist/performer webpage",
259
- "WOAS" => "Official audio source webpage",
260
- "WORS" => "Official internet radio station homepage",
261
- "WPAY" => "Payment",
262
- "WPUB" => "Publishers official webpage",
263
- "WXXX" => "User defined URL link frame"
264
- }
265
-
266
- TAGSIZE = 128
267
- #MAX_FRAME_COUNT = 6 #number of frame to read for encoder detection
268
-
269
- # mpeg version = 1 or 2
270
- attr_reader(:mpeg_version)
271
-
272
- # layer = 1, 2, or 3
273
- attr_reader(:layer)
274
-
275
- # bitrate in kbps
276
- attr_reader(:bitrate)
277
-
278
- # samplerate in Hz
279
- attr_reader(:samplerate)
280
-
281
- # channel mode => "Stereo", "JStereo", "Dual Channel" or "Single Channel"
282
- attr_reader(:channel_mode)
283
-
284
- # variable bitrate => true or false
285
- attr_reader(:vbr)
286
-
287
- # length in seconds as a Float
288
- attr_reader(:length)
289
-
290
- # error protection => true or false
291
- attr_reader(:error_protection)
292
-
293
- #a sort of "universal" tag, regardless of the tag version, 1 or 2, with the same keys as @tag1
294
- attr_reader(:tag)
295
-
296
- # id3v1 tag has a Hash. You can modify it, it will be written when calling
297
- # "close" method.
298
- attr_accessor(:tag1)
299
-
300
- # id3v2 tag as a Hash
301
- attr_reader(:tag2)
302
-
303
- # the original filename
304
- attr_reader(:filename)
305
-
306
- # Moved hastag1? and hastag2? to be booleans
307
- attr_reader(:hastag1, :hastag2)
308
-
309
- # Test the presence of an id3v1 tag in file +filename+
310
- def self.hastag1?(filename)
311
- File.open(filename) { |f|
312
- f.seek(-TAGSIZE, File::SEEK_END)
313
- f.read(3) == "TAG"
314
- }
315
- end
316
-
317
- # Test the presence of an id3v2 tag in file +filename+
318
- def self.hastag2?(filename)
319
- File.open(filename) { |f|
320
- f.read(3) == "ID3"
321
- }
322
- end
323
-
324
-
325
- # Remove id3v1 tag from +filename+
326
- def self.removetag1(filename)
327
- if self.hastag1?(filename)
328
- newsize = File.size(filename) - TAGSIZE
329
- File.open(filename, "r+") { |f| f.truncate(newsize) }
330
- end
331
- end
332
-
333
- # Instantiate a new Mp3Info object with name +filename+
334
- def initialize(filename)
335
- $stderr.puts("#{self.class}::new() does not take block; use #{self.class}::open() instead") if block_given?
336
- raise(Mp3InfoError, "empty file") unless File.stat(filename).size? #FIXME
337
- @filename = filename
338
- @hastag1, @hastag2 = false
339
- @tag = Hash.new
340
- @tag1 = Hash.new
341
- @tag2 = Hash.new
342
-
343
- @file = File.new(filename, "rb")
344
- parse_tags
345
- @tag_orig = @tag1.dup
346
-
347
- #creation of a sort of "universal" tag, regardless of the tag version
348
- if hastag2?
349
- h = {
350
- "title" => "TIT2",
351
- "artist" => "TPE1",
352
- "album" => "TALB",
353
- "year" => "TYER",
354
- "tracknum" => "TRCK",
355
- "comments" => "COMM",
356
- "genre" => 255,
357
- "genre_s" => "TCON"
358
- }
359
-
360
- h.each { |k, v| @tag[k] = @tag2[v] }
361
-
362
- elsif hastag1?
363
- @tag = @tag1.dup
364
- end
365
-
366
-
367
- ### extracts MPEG info from MPEG header and stores it in the hash @mpeg
368
- ### head (fixnum) = valid 4 byte MPEG header
369
-
370
- found = false
371
-
372
- 5.times do
373
- head = find_next_frame()
374
- @mpeg_version = [2, 1][head[19]]
375
- @layer = LAYER[head.bits(18,17)]
376
- next if @layer.nil?
377
- @bitrate = BITRATE[@mpeg_version-1][@layer-1][head.bits(15,12)-1]
378
- @error_protection = head[16] == 0 ? true : false
379
- @samplerate = SAMPLERATE[@mpeg_version-1][head.bits(11,10)]
380
- @padding = (head[9] == 1 ? true : false)
381
- @channel_mode = CHANNEL_MODE[@channel_num = head.bits(7,6)]
382
- @copyright = (head[3] == 1 ? true : false)
383
- @original = (head[2] == 1 ? true : false)
384
- @vbr = false
385
- found = true
386
- break
387
- end
388
-
389
- raise(Mp3InfoError, "Cannot find good frame") unless found
390
-
391
-
392
- seek = @mpeg_version == 1 ?
393
- (@channel_num == 3 ? 17 : 32) :
394
- (@channel_num == 3 ? 9 : 17)
395
-
396
- @file.seek(seek, IO::SEEK_CUR)
397
-
398
- vbr_head = @file.read(4)
399
- if vbr_head == "Xing"
400
- flags = @file.get32bits
401
- @streamsize = @frames = 0
402
- flags[1] == 1 and @frames = @file.get32bits
403
- flags[2] == 1 and @streamsize = @file.get32bits
404
- # currently this just skips the TOC entries if they're found
405
- @file.seek(100, IO::SEEK_CUR) if flags[0] == 1
406
- @vbr_quality = @file.get32bits if flags[3] == 1
407
- @length = (26/1000.0)*@frames
408
- @bitrate = (((@streamsize/@frames)*@samplerate)/144) >> 10
409
- @vbr = true
410
- else
411
- # for cbr, calculate duration with the given bitrate
412
- @streamsize = @file.stat.size - (@hastag1 ? TAGSIZE : 0) - (@hastag2 ? @tag2["length"] : 0)
413
- @length = ((@streamsize << 3)/1000.0)/@bitrate
414
- if @tag2["TLEN"]
415
- # but if another duration is given and it isn't close (within 5%)
416
- # assume the mp3 is vbr and go with the given duration
417
- tlen = (@tag2["TLEN"].to_i)/1000
418
- percent_diff = ((@length.to_i-tlen)/tlen.to_f)
419
- if percent_diff.abs > 0.05
420
- # without the xing header, this is the best guess without reading
421
- # every single frame
422
- @vbr = true
423
- @length = @tag2["TLEN"].to_i/1000
424
- @bitrate = (@streamsize / @bitrate) >> 10
425
- end
426
- end
427
- end
428
- end
429
-
430
- # "block version" of Mp3Info::new()
431
- def self.open(filename)
432
- m = self.new(filename)
433
- ret = nil
434
- if block_given?
435
- begin
436
- ret = yield(m)
437
- ensure
438
- m.close
439
- end
440
- else
441
- ret = m
442
- end
443
- ret
444
- end
445
-
446
- # Remove id3v1 from mp3
447
- def removetag1
448
- if hastag1?
449
- newsize = @file.stat.size(filename) - TAGSIZE
450
- @file.truncate(newsize)
451
- @tag1.clear
452
- end
453
- self
454
- end
455
-
456
- # Has file an id3v1 or v2 tag? true or false
457
- def hastag?
458
- @hastag1 or @hastag2
459
- end
460
-
461
- # Has file an id3v1 tag? true or false
462
- def hastag1?
463
- @hastag1
464
- end
465
-
466
- # Has file an id3v2 tag? true or false
467
- def hastag2?
468
- @hastag2
469
- end
470
-
471
-
472
- # Flush pending modifications to tags and close the file
473
- def close
474
- return if @file.nil?
475
- if @tag1 != @tag_orig
476
- @tag_orig.update(@tag1)
477
- #puts "@tag_orig: #{@tag_orig.inspect}"
478
- @file.reopen(@filename, 'rb+')
479
- @file.seek(-TAGSIZE, File::SEEK_END)
480
- t = @file.read(3)
481
- if t != 'TAG'
482
- #append new tag
483
- @file.seek(0, File::SEEK_END)
484
- @file.write('TAG')
485
- end
486
- str = [
487
- @tag_orig["title"]||"",
488
- @tag_orig["artist"]||"",
489
- @tag_orig["album"]||"",
490
- ((@tag_orig["year"] != 0) ? ("%04d" % @tag_orig["year"]) : "\0\0\0\0"),
491
- @tag_orig["comments"]||"",
492
- 0,
493
- @tag_orig["tracknum"]||0,
494
- @tag_orig["genre"]||255
495
- ].pack("Z30Z30Z30Z4Z28CCC")
496
- @file.write(str)
497
- end
498
- @file.close
499
- @file = nil
500
- end
501
-
502
- # inspect inside Mp3Info
503
- def to_s
504
- s = "MPEG #{@mpeg_version} Layer #{@layer} #{@vbr ? "VBR" : "CBR"} #{@bitrate} Kbps #{@channel_mode} #{@samplerate} Hz length #{@length} sec. error protection #{@error_protection} "
505
- s << "tag1: "+@tag1.inspect+"\n" if @hastag1
506
- s << "tag2: "+@tag2.inspect+"\n" if @hastag2
507
- s
508
- end
509
-
510
-
511
- private
512
-
513
- ### parses the id3 tags of the currently open @file
514
- def parse_tags
515
- return if @file.stat.size < TAGSIZE # file is too small
516
- @file.seek(0)
517
- f3 = @file.read(3)
518
- gettag1 if f3 == "TAG" # v1 tag at beginning
519
- gettag2 if f3 == "ID3" # v2 tag at beginning
520
- unless @hastag1 # v1 tag at end
521
- # this preserves the file pos if tag2 found, since gettag2 leaves
522
- # the file at the best guess as to the first MPEG frame
523
- pos = (@hastag2 ? @file.pos : 0)
524
- # seek to where id3v1 tag should be
525
- @file.seek(-TAGSIZE, IO::SEEK_END)
526
- gettag1 if @file.read(3) == "TAG"
527
- @file.seek(pos)
528
- end
529
- end
530
-
531
- ### reads in id3 field strings, stripping out non-printable chars
532
- ### len (fixnum) = number of chars in field
533
- ### returns string
534
- def read_id3_string(len)
535
- #FIXME handle unicode strings
536
- #return @file.read(len)
537
- s = ""
538
- len.times do
539
- c = @file.getc
540
- # only append printable characters
541
- s << c if c >= 32 and c < 254
542
- end
543
- return s.strip
544
- #return (s[0..2] == "eng" ? s[3..-1] : s)
545
- end
546
-
547
- ### gets id3v1 tag information from @file
548
- ### assumes @file is pointing to char after "TAG" id
549
- def gettag1
550
- @hastag1 = true
551
- @tag1["title"] = read_id3_string(30)
552
- @tag1["artist"] = read_id3_string(30)
553
- @tag1["album"] = read_id3_string(30)
554
- year_t = read_id3_string(4).to_i
555
- @tag1["year"] = year_t unless year_t == 0
556
- comments = @file.read(30)
557
- if comments[-2] == 0
558
- @tag1["tracknum"] = comments[-1].to_i
559
- comments.chop! #remove the last char
560
- end
561
- #@tag1["comments"] = comments.sub!(/\0.*$/, '')
562
- @tag1["comments"] = comments.strip
563
- @tag1["genre"] = @file.getc
564
- @tag1["genre_s"] = GENRES[@tag1["genre"]] || ""
565
- end
566
-
567
- ### gets id3v2 tag information from @file
568
- def gettag2
569
- @file.seek(3)
570
- version_maj, version_min, flags = @file.read(3).unpack("CCB4")
571
- unsync, ext_header, experimental, footer = (0..3).collect { |i| flags[i].chr == '1' }
572
- return unless [2, 3, 4].include?(version_maj)
573
- @hastag2 = true
574
- @tag2["version"] = "2.#{version_maj}.#{version_min}"
575
- tag2_len = @file.get_syncsafe
576
- case version_maj
577
- when 2
578
- read_id3v2_2_frames(tag2_len)
579
- when 3,4
580
- # seek past extended header if present
581
- @file.seek(@file.get_syncsafe - 4, IO::SEEK_CUR) if ext_header
582
- read_id3v2_3_frames(tag2_len)
583
- end
584
- tag2["length"] = @file.pos
585
- # we should now have @file sitting at the first MPEG frame
586
- end
587
-
588
- ### runs thru @file one char at a time looking for best guess of first MPEG
589
- ### frame, which should be first 0xff byte after id3v2 padding zero's
590
- ### returns true
591
- def v2_end?
592
- until @file.getc == 0xff
593
- end
594
- @file.seek(-1, IO::SEEK_CUR)
595
- true
596
- end
597
-
598
- ### reads id3 ver 2.3.x/2.4.x frames and adds the contents to @tag2 hash
599
- ### tag2_len (fixnum) = length of entire id3v2 data, as reported in header
600
- ### NOTE: the id3v2 header does not take padding zero's into consideration
601
- def read_id3v2_3_frames(tag2_len)
602
- v2end_found = false
603
- until v2end_found # there are 2 ways to end the loop
604
- name = @file.read(4)
605
- if name[0] == 0
606
- @file.seek(-4, IO::SEEK_CUR) # 1. find a padding zero,
607
- v2end_found = v2_end? # so we seek to end of zeros
608
- else
609
- size = @file.get32bits
610
- @file.seek(2, IO::SEEK_CUR) # skip flags
611
- add_value_to_tag2(name, size)
612
- # case name
613
- # when /T[A-Z]+|COMM/
614
- # data = read_id3_string(size-1)
615
- # add_value_to_tag2(name, data)
616
- # else
617
- # @file.seek(size-1, IO::SEEK_CUR)
618
- # end
619
- v2end_found = true if @file.pos >= tag2_len # 2. reach length from header
620
- end
621
- end
622
- end
623
-
624
- ### reads id3 ver 2.2.x frames and adds the contents to @tag2 hash
625
- ### tag2_len (fixnum) = length of entire id3v2 data, as reported in header
626
- ### NOTE: the id3v2 header does not take padding zero's into consideration
627
- def read_id3v2_2_frames(tag2_len)
628
- v2end_found = false
629
- until v2end_found
630
- name = @file.read(3)
631
- if name[0] == 0
632
- @file.seek(-3, IO::SEEK_CUR)
633
- v2end_found = v2_end?
634
- else
635
- size = (@file.getc << 16) + (@file.getc << 8) + @file.getc
636
- add_value_to_tag2(name, size)
637
- v2end_found = true if @file.pos >= tag2_len
638
- end
639
- end
640
- end
641
-
642
- ### Add data to tag2["name"]
643
- ### read lang_encoding, decode data if unicode and
644
- ### create an array if the key ever exists in the tag
645
- def add_value_to_tag2(name, size)
646
- lang_encoding = @file.getc # language encoding bit 0 for iso_8859_1, 1 for unicode
647
- data = size == 0 ? "" : @file.read(size-1)
648
-
649
- if lang_encoding == 1 and name[0] == ?T
650
- require "iconv"
651
-
652
- #strip byte-order bytes at the beginning of the unicode string if they exists
653
- data[0..3] =~ /^[\xff\xfe]+$/ and data = data[2..-1]
654
-
655
- data = Iconv.iconv("ISO-8859-1", "UNICODE", data)[0]
656
- end
657
-
658
- if @tag2.keys.include?(name)
659
- unless @tag2[name].is_a?(Array)
660
- keep = @tag2[name]
661
- @tag2[name] = []
662
- @tag2[name] << keep
663
- end
664
- @tag2[name] << data
665
- else
666
- @tag2[name] = data
667
- end
668
- end
669
-
670
- ### reads through @file from current pos until it finds a valid MPEG header
671
- ### returns the MPEG header as FixNum
672
- def find_next_frame
673
- # @file will now be sitting at the best guess for where the MPEG frame is.
674
- # It should be at byte 0 when there's no id3v2 tag.
675
- # It should be at the end of the id3v2 tag or the zero padding if there
676
- # is a id3v2 tag.
677
- start_pos = @file.pos
678
- dummyproof = @file.stat.size - @file.pos
679
- dummyproof.times do |i|
680
- if @file.getc == 0xff
681
- head = 0xff000000 + (@file.getc << 16) + (@file.getc << 8) + @file.getc
682
- if check_head(head)
683
- return head
684
- else
685
- @file.seek(-3, IO::SEEK_CUR)
686
- end
687
- end
688
- end
689
- raise Mp3InfoError
690
- end
691
-
692
- ### checks the given header to see if it is valid
693
- ### head (fixnum) = 4 byte value to test for MPEG header validity
694
- ### returns true if valid, false if not
695
- def check_head(head)
696
- return false if head & 0xffe00000 != 0xffe00000 # 11 bit MPEG frame sync
697
- return false if head & 0x00060000 == 0x00060000 # 2 bit layer type
698
- return false if head & 0x0000f000 == 0x0000f000 # 4 bit bitrate
699
- return false if head & 0x0000f000 == 0x00000000 # free format bitstream
700
- return false if head & 0x00000c00 == 0x00000c00 # 2 bit frequency
701
- return false if head & 0xffff0000 == 0xfffe0000
702
- true
703
- end
704
-
705
- end
706
-
707
- if $0 == __FILE__
708
- while filename = ARGV.shift
709
- begin
710
- info = Mp3Info.new(filename)
711
- puts filename
712
- #puts "MPEG #{info.mpeg_version} Layer #{info.layer} #{info.vbr ? "VBR" : "CBR"} #{info.bitrate} Kbps \
713
- #{info.channel_mode} #{info.samplerate} Hz length #{info.length} sec."
714
- puts info
715
- rescue Mp3InfoError => e
716
- puts "#{filename}\nERROR: #{e}"
717
- end
718
- puts
719
- end
720
- end