Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit df12a567d6c74d8935312ef492288de3ba5b08fd @jeremyevans committed
482 LICENSE
@@ -0,0 +1,482 @@
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
4 extconf.rb
@@ -0,0 +1,4 @@
+require 'mkmf'
+find_library('ogg', 'ogg_sync_init')
+find_library('vorbis', 'vorbis_comment_init')
+create_makefile("vorbis_comment_ext")
BIN test/blank.ogg
Binary file not shown.
BIN test/corrupt.ogg
Binary file not shown.
BIN test/lt8k.ogg
Binary file not shown.
BIN test/manyfields.ogg
Binary file not shown.
130 test/test_vorbis_comment.rb
@@ -0,0 +1,130 @@
+#!/usr/bin/env ruby
+require 'rubygems'
+require 'vorbis_comment'
+require 'fileutils'
+require 'test/unit'
+
+begin
+ VorbisComment.new(File.join(File.dirname(__FILE__), 'lt8k.ogg')).fields
+rescue VorbisComment::InvalidDataError
+ puts('The linked version of libvorbis has problems with files < 8K in size')
+end
+
+class VorbisCommentTest < Test::Unit::TestCase
+ def vc(filename)
+ VorbisComment.new(File.join(File.dirname(__FILE__), filename))
+ end
+
+ def work_with_copy(filename, &block)
+ new_name = File.join(File.dirname(__FILE__), "copy-#{filename}")
+ FileUtils.cp(File.join(File.dirname(__FILE__), filename), new_name)
+ begin
+ result = yield new_name
+ ensure
+ File.delete(new_name)
+ end
+ result
+ end
+
+ def test_initialize
+ assert_equal File.join(File.dirname(__FILE__), 'title.ogg'), vc('title.ogg').filename
+ # Nonexistent files don't raise an error until they are accessed for the fields
+ assert_equal 'nonexistant.ogg', VorbisComment.new('nonexistant.ogg').filename
+ # Corrupt files don't raise an error until they are parsed for the fields
+ assert_equal File.join(File.dirname(__FILE__), 'corrupt.ogg'), vc('corrupt.ogg').filename
+ end
+
+ def test_exists?
+ assert_equal true, vc('title.ogg').exists?
+ assert_equal true, vc('manyfields.ogg').exists?
+ # Blank tags still exist
+ assert_equal true, vc('blank.ogg').exists?
+ # Corrupt tags are considered not to exist
+ assert_equal false, vc('corrupt.ogg').exists?
+ # Files that aren't ogg files will be treated similarly
+ assert_equal false, vc('test_vorbis_comment.rb').exists?
+ # But nonexistant files raise an OpenError
+ assert_raises(VorbisComment::OpenError){vc('nonexistant.ogg').exists?}
+ end
+
+ def test_fields
+ assert_equal Hash[], vc('blank.ogg').fields
+ assert_equal Hash['title'=>['Silence']], vc('title.ogg').fields
+ assert_equal Hash[{"ARTIST"=>["Test Artist 1", "Test Artist 2"], "TrackNumber"=>["1"], "Album"=>["Test Album"], "Title"=>["Test Title"], "Date"=>["1999-12-31"], "comment"=>[""]}], vc('manyfields.ogg').fields
+ assert_raises(VorbisComment::InvalidDataError){vc('corrupt.ogg').fields}
+ assert_raises(VorbisComment::OpenError){vc('nonexistant.ogg').fields}
+ end
+
+ def test_remove!
+ assert_equal Hash[], work_with_copy('blank.ogg'){|filename| VorbisComment.new(filename).remove!}
+ assert_equal Hash[], work_with_copy('title.ogg'){|filename| VorbisComment.new(filename).remove!}
+ assert_equal Hash[], work_with_copy('manyfields.ogg'){|filename| VorbisComment.new(filename).remove!}
+ assert_raises(VorbisComment::InvalidDataError){work_with_copy('corrupt.ogg'){|filename| VorbisComment.new(filename).remove!}}
+ assert_raises(VorbisComment::OpenError){VorbisComment.new('nonexistant.ogg').remove!}
+ end
+
+ def test_pretty_print
+ assert_equal "", vc('blank.ogg').pretty_print
+ assert_equal "title: Silence", vc('title.ogg').pretty_print
+ assert_equal "ARTIST: Test Artist 1, Test Artist 2\nAlbum: Test Album\nDate: 1999-12-31\nTitle: Test Title\nTrackNumber: 1\ncomment: ", vc('manyfields.ogg').pretty_print
+ assert_equal "CORRUPT TAG!", vc('corrupt.ogg').pretty_print
+ assert_equal "CORRUPT TAG!", vc('test_vorbis_comment.rb').pretty_print
+ end
+
+ def test_update
+ f = {'Blah'=>['Blah']}
+ assert_raises(VorbisComment::InvalidDataError){work_with_copy('corrupt.ogg'){|filename| VorbisComment.new(filename).update{|fields| fields.merge!(f)}}}
+ assert_raises(VorbisComment::OpenError){VorbisComment.new('nonexistant.ogg').update{|fields| fields.merge!(f)}}
+
+ g = {'x'=>'y', 'y'=>:z, :z=>[:A], :zz=>[['A'], [[[:b]], 'c']]}
+ h = {'x'=>['y'], 'y'=>['z'], 'z'=>['A'], 'zz'=>['A', 'bc']}
+
+ %w'blank.ogg title.ogg manyfields.ogg'.each do |fname|
+ work_with_copy(fname) do |filename|
+ v = VorbisComment.new(filename)
+ fx = v.fields.merge(f)
+ assert_equal fx, v.update{|fields| fields.merge!(f)}
+ assert_equal fx, v.fields
+ v = VorbisComment.new(filename)
+ assert_equal fx, v.fields
+ gx = v.fields.merge(g)
+ assert_equal gx, v.update{|fields| fields.merge!(g)}
+ v = VorbisComment.new(filename)
+ hx = v.fields.merge(h)
+ assert_equal hx, VorbisComment.new(filename).fields
+ end
+ end
+ end
+
+ def test_bad_keys_and_values
+ work_with_copy('blank.ogg') do |filename|
+ v = VorbisComment.new(filename)
+ # Test for bad keys
+ (("\x00".."\x1f").to_a + ['='] + ("\x7e".."\xff").to_a).each do |c|
+ assert_raises(VorbisComment::InvalidCommentError){v.update{|fields| fields[c] = 'Blah'}}
+ end
+ # Test for bad vales (Invalid UTF8)
+ assert_raises(VorbisComment::InvalidCommentError){v.update{|fields| fields['Blah'] = "\x81\x81"}}
+ end
+ end
+
+ def test_add_to_fields_and_normalize_fields
+ work_with_copy('blank.ogg') do |filename|
+ v = VorbisComment.new(filename)
+ assert_equal Hash[], v.fields
+ assert_equal [], v.send(:normalize_fields)
+ v.send(:add_to_fields, 'A', 'b')
+ assert_equal CICPHash['A'=>['b']], v.fields
+ assert_equal [['A', 'b']], v.send(:normalize_fields)
+ v.send(:add_to_fields, :a, ['C'])
+ assert_equal CICPHash['A'=>['b',['C']]], v.fields
+ assert_equal [['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
+ v.send(:add_to_fields, 12, 42)
+ assert_equal CICPHash['A'=>['b',['C']], 12=>[42]], v.fields
+ assert_equal [['12', '42'], ['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
+ v.update{}
+ assert_equal CICPHash['A'=>['C','b'], '12'=>['42']], VorbisComment.new(filename).fields
+ assert_equal [['12', '42'], ['A', 'C'], ['A', 'b']], v.send(:normalize_fields)
+ end
+ end
+end
BIN test/title.ogg
Binary file not shown.
583 vcedit.c
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2000-2001 Michael Smith (msmith at xiph org)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+#include <assert.h>
+
+#include "vcedit.h"
+
+#define CHUNKSIZE 4096
+
+struct vcedit_state_St {
+ int refcount;
+
+ ogg_sync_state oy;
+ ogg_stream_state os;
+
+ vorbis_comment vc;
+ vorbis_info vi;
+
+ FILE *in;
+ mode_t file_mode;
+
+ bool opened;
+ long serial;
+
+ ogg_packet packet_main;
+ ogg_packet packet_code_books;
+
+ char *vendor;
+ int prevW;
+
+ bool extra_page;
+ bool eos;
+
+ char filename[0];
+};
+
+static void
+ogg_packet_init (ogg_packet *p, unsigned char *buf, long len)
+{
+ p->packet = buf;
+ p->bytes = len;
+
+ p->b_o_s = p->e_o_s = 0;
+ p->granulepos = p->packetno = 0;
+}
+
+static bool
+ogg_packet_dup_data (ogg_packet *p)
+{
+ unsigned char *tmp;
+
+ tmp = malloc (p->bytes);
+ if (!tmp)
+ return false;
+
+ memcpy (tmp, p->packet, p->bytes);
+ p->packet = tmp;
+
+ return true;
+}
+
+static void
+vcedit_state_free (vcedit_state *s)
+{
+ free (s->vendor);
+
+ if (s->in) {
+ fclose (s->in);
+ s->in = NULL;
+ }
+
+ free (s);
+}
+
+static bool
+vcedit_state_init (vcedit_state *s, const char *filename)
+{
+ s->refcount = 1;
+
+ ogg_packet_init (&s->packet_main, NULL, 0);
+ ogg_packet_init (&s->packet_code_books, NULL, 0);
+
+ strcpy (s->filename, filename);
+
+ return true;
+}
+
+vcedit_state *
+vcedit_state_new (const char *filename)
+{
+ vcedit_state *s;
+ size_t len;
+
+ len = strlen (filename);
+ if (len > PATH_MAX)
+ return NULL;
+
+ s = malloc (sizeof (vcedit_state) + len + 1);
+ if (!s)
+ return NULL;
+
+ memset (s, 0, sizeof (vcedit_state));
+
+ if (!vcedit_state_init (s, filename)) {
+ vcedit_state_free (s);
+ return NULL;
+ }
+
+ return s;
+}
+
+vorbis_comment *
+vcedit_comments (vcedit_state *s)
+{
+ return s->opened ? &s->vc : NULL;
+}
+
+static void
+vcedit_clear_internals (vcedit_state *s)
+{
+ ogg_stream_clear (&s->os);
+ ogg_sync_clear (&s->oy);
+
+ vorbis_info_clear (&s->vi);
+ vorbis_comment_clear (&s->vc);
+
+ free (s->vendor);
+ s->vendor = NULL;
+
+ ogg_packet_clear (&s->packet_main);
+ ogg_packet_clear (&s->packet_code_books);
+
+ s->serial = 0;
+ s->opened = false;
+}
+
+void
+vcedit_state_ref (vcedit_state *s)
+{
+ s->refcount++;
+}
+
+void
+vcedit_state_unref (vcedit_state *s)
+{
+ if (--s->refcount)
+ return;
+
+ if (s->opened)
+ vcedit_clear_internals (s);
+
+ vcedit_state_free (s);
+}
+
+/* Next two functions pulled straight from libvorbis, apart from one change
+ * - we don't want to overwrite the vendor string.
+ */
+static void
+_v_writestring (oggpack_buffer *o, const char *s, int len)
+{
+ while (len--)
+ oggpack_write (o, *s++, 8);
+}
+
+static bool
+write_comments (vcedit_state *s, ogg_packet *packet)
+{
+ oggpack_buffer opb;
+ size_t len;
+ int i;
+
+ ogg_packet_init (packet, NULL, 0);
+
+ oggpack_writeinit (&opb);
+
+ /* preamble */
+ oggpack_write (&opb, 0x03, 8);
+ _v_writestring (&opb, "vorbis", 6);
+
+ /* vendor */
+ len = strlen (s->vendor);
+ oggpack_write (&opb, len, 32);
+ _v_writestring (&opb, s->vendor, len);
+
+ /* comments */
+ oggpack_write (&opb, s->vc.comments, 32);
+
+ for (i = 0; i < s->vc.comments; i++)
+ if (!s->vc.user_comments[i])
+ oggpack_write (&opb, 0, 32);
+ else {
+ oggpack_write (&opb, s->vc.comment_lengths[i], 32);
+ _v_writestring (&opb, s->vc.user_comments[i],
+ s->vc.comment_lengths[i]);
+ }
+
+ oggpack_write (&opb, 1, 1);
+
+ packet->bytes = oggpack_bytes (&opb);
+
+ packet->packet = _ogg_malloc (packet->bytes);
+ if (!packet->packet)
+ return false;
+
+ memcpy (packet->packet, opb.buffer, packet->bytes);
+
+ oggpack_writeclear (&opb);
+
+ return true;
+}
+
+static int
+_blocksize (vcedit_state *s, ogg_packet *p)
+{
+ int this, ret = 0;
+
+ this = vorbis_packet_blocksize (&s->vi, p);
+
+ if (s->prevW)
+ ret = (this + s->prevW) / 4;
+
+ s->prevW = this;
+
+ return ret;
+}
+
+static int
+_fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
+{
+ char *buffer;
+ int result, bytes;
+
+ result = ogg_stream_packetout (&s->os, p);
+ if (result == 1)
+ return 1;
+
+ if (s->eos)
+ return 0;
+
+ while (ogg_sync_pageout (&s->oy, page) != 1) {
+ buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+ bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+ ogg_sync_wrote (&s->oy, bytes);
+
+ if (!bytes && (feof (s->in) || ferror (s->in)))
+ return 0;
+ }
+
+ if (ogg_page_eos (page))
+ s->eos = true;
+ else if (ogg_page_serialno (page) != s->serial) {
+ s->eos = true;
+ s->extra_page = true;
+
+ return 0;
+ }
+
+ ogg_stream_pagein (&s->os, page);
+
+ return _fetch_next_packet (s, p, page);
+}
+
+vcedit_error
+vcedit_open (vcedit_state *s)
+{
+ vcedit_error ret;
+ ogg_packet packet_comments, *header = &packet_comments;
+ ogg_page page;
+ struct stat st;
+ char *buffer;
+ size_t bytes, total = 0;
+ int i = 0;
+
+ s->in = fopen (s->filename, "rb");
+ if (!s->in)
+ return VCEDIT_ERR_OPEN;
+
+ s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode;
+
+ ogg_sync_init (&s->oy);
+
+ do {
+ /* Bail if we don't find data in the first 40 kB */
+ if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) {
+ ogg_sync_clear (&s->oy);
+
+ return VCEDIT_ERR_INVAL;
+ }
+
+ buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+
+ bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+ total += bytes;
+
+ ogg_sync_wrote (&s->oy, bytes);
+ } while (ogg_sync_pageout (&s->oy, &page) != 1);
+
+ s->serial = ogg_page_serialno (&page);
+
+ ogg_stream_init (&s->os, s->serial);
+ vorbis_info_init (&s->vi);
+ vorbis_comment_init (&s->vc);
+
+ if (ogg_stream_pagein (&s->os, &page) < 0) {
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ if (ogg_stream_packetout (&s->os, &s->packet_main) != 1) {
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ if (!ogg_packet_dup_data (&s->packet_main)) {
+ s->packet_main.packet = NULL;
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ if (vorbis_synthesis_headerin (&s->vi, &s->vc, &s->packet_main) < 0) {
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ ogg_packet_init (&packet_comments, NULL, 0);
+
+ while (i < 2) {
+ if (feof (s->in) || ferror (s->in)) {
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ while (i < 2) {
+ int result;
+
+ result = ogg_sync_pageout (&s->oy, &page);
+ if (!result)
+ break; /* Too little data so far */
+
+ if (result != 1)
+ continue;
+
+ ogg_stream_pagein (&s->os, &page);
+
+ while (i < 2) {
+ result = ogg_stream_packetout (&s->os, header);
+ if (!result)
+ break;
+
+ if (result != 1) {
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ if (i++ == 1 && !ogg_packet_dup_data (header)) {
+ header->packet = NULL;
+ ret = VCEDIT_ERR_INVAL;
+ goto err;
+ }
+
+ vorbis_synthesis_headerin (&s->vi, &s->vc, header);
+
+ header = &s->packet_code_books;
+ }
+ }
+
+ buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+
+ bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+ ogg_sync_wrote (&s->oy, bytes);
+ }
+
+ /* Copy the vendor tag */
+ s->vendor = strdup (s->vc.vendor);
+
+ /* Headers are done! */
+ s->opened = true;
+
+ return VCEDIT_ERR_SUCCESS;
+
+err:
+ vcedit_clear_internals (s);
+
+ return ret;
+}
+
+static bool
+write_data (const void *buf, size_t size, size_t nmemb, FILE *stream)
+{
+ while (nmemb > 0) {
+ size_t w;
+
+ w = fwrite (buf, size, nmemb, stream);
+ if (!w && ferror (stream))
+ return false;
+
+ nmemb -= w;
+ buf += size * w;
+ }
+
+ return true;
+}
+
+static bool
+write_page (FILE *f, ogg_page *p)
+{
+ return write_data (p->header, 1, p->header_len, f) &&
+ write_data (p->body, 1, p->body_len, f);
+}
+
+vcedit_error
+vcedit_write (vcedit_state *s)
+{
+ ogg_stream_state stream;
+ ogg_packet packet;
+ ogg_page page_out, page_in;
+ ogg_int64_t granpos = 0;
+ FILE *out;
+ char *buffer, tmpfile[PATH_MAX];
+ bool success = false, need_flush = false, need_out = false;
+ int fd, result, bytes;
+
+ if (!s->opened)
+ return VCEDIT_ERR_INVAL;
+
+ strcpy (tmpfile, s->filename);
+ strcat (tmpfile, ".XXXXXX");
+
+ fd = mkstemp (tmpfile);
+ if (fd == -1)
+ return VCEDIT_ERR_TMPFILE;
+
+ out = fdopen (fd, "wb");
+ if (!out) {
+ unlink (tmpfile);
+ close (fd);
+
+ return VCEDIT_ERR_TMPFILE;
+ }
+
+ s->prevW = 0;
+ s->extra_page = s->eos = false;
+
+ ogg_stream_init (&stream, s->serial);
+
+ /* write "main" packet */
+ s->packet_main.b_o_s = 1;
+ ogg_stream_packetin (&stream, &s->packet_main);
+ s->packet_main.b_o_s = 0;
+
+ /* prepare and write comments */
+ if (!write_comments (s, &packet)) {
+ ogg_stream_clear (&stream);
+ unlink (tmpfile);
+ fclose (out);
+
+ return VCEDIT_ERR_INVAL;
+ }
+
+ ogg_stream_packetin (&stream, &packet);
+ ogg_packet_clear (&packet);
+
+ /* write codebooks */
+ ogg_stream_packetin (&stream, &s->packet_code_books);
+
+ while (ogg_stream_flush (&stream, &page_out))
+ if (!write_page (out, &page_out))
+ goto cleanup;
+
+ while (_fetch_next_packet (s, &packet, &page_in)) {
+ bool write = false;
+ int size;
+
+ size = _blocksize (s, &packet);
+ granpos += size;
+
+ if (need_flush)
+ write = ogg_stream_flush (&stream, &page_out);
+ else if (need_out)
+ write = ogg_stream_pageout (&stream, &page_out);
+
+ if (write && !write_page (out, &page_out))
+ goto cleanup;
+
+ need_flush = need_out = false;
+
+ if (packet.granulepos == -1) {
+ packet.granulepos = granpos;
+ ogg_stream_packetin (&stream, &packet);
+ } else {
+ /* granulepos is set, validly. Use it, and force a flush to
+ * account for shortened blocks (vcut) when appropriate
+ */
+ if (granpos > packet.granulepos) {
+ granpos = packet.granulepos;
+ ogg_stream_packetin (&stream, &packet);
+ need_flush = true;
+ } else {
+ ogg_stream_packetin (&stream, &packet);
+ need_out = true;
+ }
+ }
+ }
+
+ stream.e_o_s = 1;
+
+ while (ogg_stream_flush (&stream, &page_out))
+ if (!write_page (out, &page_out))
+ goto cleanup;
+
+ if (s->extra_page && !write_page (out, &page_in))
+ goto cleanup;
+
+ /* clear it, because not all paths to here do */
+ s->eos = false;
+
+ do {
+ /* We copy the rest of the stream (other logical streams)
+ * through, a page at a time.
+ */
+ while ((result = ogg_sync_pageout (&s->oy, &page_out)))
+ if (result == 1 && !write_page (out, &page_out))
+ goto cleanup;
+
+ buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE);
+ bytes = fread (buffer, 1, CHUNKSIZE, s->in);
+ ogg_sync_wrote (&s->oy, bytes);
+
+ if (ferror (s->in))
+ goto cleanup;
+ } while (bytes || !feof (s->in));
+
+ s->eos = success = true;
+
+cleanup:
+ fclose (s->in);
+
+ if (!success) {
+ unlink (tmpfile);
+ fclose (out);
+ } else {
+ fclose (out);
+ unlink (s->filename);
+ rename (tmpfile, s->filename);
+ chmod (s->filename, s->file_mode);
+ }
+
+ ogg_stream_clear (&stream);
+
+ if (!s->eos)
+ return VCEDIT_ERR_INVAL;
+
+ vcedit_clear_internals (s);
+
+ return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ?
+ VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN;
+}
52 vcedit.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2000-2001 Michael Smith (msmith at xiph org)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#ifndef __VCEDIT_H
+#define __VCEDIT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+
+typedef enum {
+ VCEDIT_ERR_SUCCESS = 0,
+ VCEDIT_ERR_OPEN,
+ VCEDIT_ERR_INVAL,
+ VCEDIT_ERR_TMPFILE,
+ VCEDIT_ERR_REOPEN
+} vcedit_error;
+
+typedef struct vcedit_state_St vcedit_state;
+
+vcedit_state *vcedit_state_new (const char *filename);
+void vcedit_state_ref (vcedit_state *state);
+void vcedit_state_unref (vcedit_state *state);
+vorbis_comment *vcedit_comments (vcedit_state *state);
+vcedit_error vcedit_open (vcedit_state *state);
+vcedit_error vcedit_write (vcedit_state *state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __VCEDIT_H */
+
18 vorbis_comment.gemspec
@@ -0,0 +1,18 @@
+spec = Gem::Specification.new do |s|
+ s.name = "vorbis_comment"
+ s.version = "1.0.0"
+ s.author = "Jeremy Evans"
+ s.email = "code@jeremyevans.net"
+ s.homepage = "http://rubyforge.org/projects/vorbis_comment/"
+ s.platform = Gem::Platform::RUBY
+ s.summary = "Vorbis Comment Reader/Writer Library"
+ s.files = Dir["*"]
+ s.require_paths = ["."]
+ s.extensions << 'extconf.rb'
+ s.autorequire = "vorbis_comment"
+ s.test_files = Dir["test/*"]
+ s.has_rdoc = true
+ s.rdoc_options = %w'--inline-source --line-numbers'
+ s.rubyforge_project = 'vorbis_comment'
+end
+
164 vorbis_comment.rb
@@ -0,0 +1,164 @@
+#!/usr/bin/env ruby
+# This library implements a Vorbis Comment reader/writer.
+# If called from the command line, it prints out the contents of the APEv2 tag
+# for the given filename arguments.
+#
+# ruby-vorbis_comment is a pure Ruby library for manipulating Vorbis comments.
+# It wraps libvorbis and libogg, so it should be completely standards
+# compatible. Vorbis comment is the standard tagging format for Ogg Vorbis,
+# FLAC, and Speex files.
+#
+# The library includes a C extension, which may or may not build cleanly on all
+# architectures. It is developed and tested on OpenBSD i386 and amd64. If it
+# doesn't work on your machine, please try to get it to work and send in a
+# patch.
+#
+# This library tries to be API compatible with ruby-apetag, a library for
+# reading and writing APE tags, the standard tagging format for Musepack and
+# Monkey's Audio, which can also be used with MP3s as an alternative to ID3v2.
+#
+# General Use:
+#
+# require 'vorbiscomment'
+# a = VorbisComment.new('file.ogg')
+# a.exists? # if it already has an vorbis comment
+# a.fields # a CICPHash of fields, keys are strings, values are lists of strings
+# a.pretty_print # string suitable for pretty printing
+# a.update{|fields| fields['Artist']='Test Artist'; fields.delete('Year')}
+# # Update the tag with the added/changed/deleted fields
+# # Note that you should do: a.update{|fields| fields.replace('Test'=>'Test')}
+# # and NOT: a.update{|fields| fields = {'Test'=>'Test'}}
+# # You need to update/modify the fields given, not reassign it
+# a.remove! # clear the list of vorbis comments from the file
+#
+# To run the tests for the library, run test_vorbis_comment.rb.
+#
+# If you find any bugs, would like additional documentation, or want to submit a
+# patch, please use Rubyforge (http://rubyforge.org/projects/vorbis_comment/).
+# One known bug is in the libvorbis library, in that it doesn't like files less
+# than 8K in size.
+#
+# The most current source code can be accessed via anonymous SVN at
+# svn://code.jeremyevans.net/ruby-vorbis_comment/. Note that the library isn't
+# modified on a regular basis, so it is unlikely to be different from the latest
+# release.
+#
+# The pure ruby part of this library is copyright Jeremy Evans and is released
+# under the MIT License.
+#
+# The C part of this library is copyright Jeremy Evans and Tilman Sauerbeck and
+# is licensed under the GNU LGPL.
+#
+# Copyright (c) 2007 Jeremy Evans
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+require 'cicphash'
+
+# Contains all of the Vorbis comment fields found in the filename/file given.
+class VorbisComment
+ attr_reader :filename
+ BAD_KEY_RE = /[\x00-\x1f=\x7e-\xff]/
+
+ # Set the filename to be used
+ def initialize(filename)
+ @filename = filename.to_s
+ end
+
+ # Check for the existance of the a vorbis comment in the file.
+ # Because all Vorbis bitstreams should have a comment, any file missing
+ # a comment probably isn't a vorbis file, or it's possible the file is
+ # corrupt.
+ def exists?
+ begin
+ fields
+ true
+ rescue InvalidDataError
+ false
+ end
+ end
+
+ # Clear all fields from the vorbis comment
+ def remove!
+ update{|fields| fields.clear}
+ end
+
+ # Get all the fields in the vorbis comment
+ def fields
+ return @fields if @fields
+ @fields = CICPHash.new
+ read_fields
+ @fields
+ end
+
+ # Return a string suitable for pretty printing, used by the command line
+ def pretty_print
+ begin
+ fields.sort.collect{|key, value| "#{key}: #{value.join(', ')}"}.join("\n")
+ rescue OpenError
+ "FILE NOT FOUND!"
+ rescue InvalidDataError, InvalidCommentError
+ "CORRUPT TAG!"
+ end
+ end
+
+ # Update the vorbis comment with the changes that occur to fields inside the
+ # block. Make sure to modify the fields given and not reassign them.
+ def update(&block)
+ yield fields
+ write_fields(normalize_fields)
+ fields
+ end
+
+ private
+ # Return a array with key/value pairs so that keys with multiple values
+ # have multiple entries. This is used mainly to make the C extension
+ # easier to code.
+ def normalize_fields
+ comments = []
+ fields.each do |key, values|
+ key = key.to_s
+ raise InvalidCommentError if key =~ BAD_KEY_RE
+ values = [values] unless values.is_a?(Array)
+ values = values.collect{|value| value.to_s}
+ values.each do |value|
+ value.unpack('U*') rescue (raise InvalidCommentError)
+ comments << [key, value]
+ end
+ end
+ comments.sort
+ end
+
+ # Add a value for the given key to the list of values for that key, creating
+ # the list if this is the first value for that key. This is used to make
+ # the C extension easier to code.
+ def add_to_fields(key, value)
+ (fields[key] ||= []) << value
+ end
+end
+
+require 'vorbis_comment_ext' # define read_fields and write_fields
+
+# If called directly from the command line, treat all arguments as filenames
+# and pretty print the vorbis comment fields for each filename.
+if __FILE__ == $0
+ ARGV.each do |filename|
+ puts filename, '-'*filename.length, VorbisComment.new(filename).pretty_print, ''
+ end
+end
161 vorbis_comment_ext.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2007 Jeremy Evans (code at jeremyevans net)
+ * Copyright (C) 2006 Tilman Sauerbeck (tilman at code-monkey de)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include <ruby.h>
+#include <st.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+
+#include "vcedit.h"
+
+static VALUE eOpen, eInvalidData, eInvalidComment, eTempFile, eReopen;
+
+/*
+ * call-seq:
+ * object.read_fields -> fields
+ *
+ * Reads the comments from the file into the appropriate data structure.
+ * Returns the fields (a precreated CICPHash). Do not call this directly.
+ */
+VALUE read_fields (VALUE self) {
+ vcedit_state *state;
+ vorbis_comment *vc;
+ VALUE fields, filename;
+ VALUE k, v;
+ int add_to_fields;
+ int i;
+
+ filename = rb_iv_get(self, "@filename");
+ state = vcedit_state_new(StringValuePtr(filename));
+ if (!state)
+ rb_raise (rb_eNoMemError, "Out of Memory");
+
+ switch (vcedit_open (state)) {
+ case VCEDIT_ERR_OPEN:
+ vcedit_state_unref(state);
+ rb_raise (eOpen, "Cannot open file");
+ case VCEDIT_ERR_INVAL:
+ vcedit_state_unref(state);
+ rb_raise (eInvalidData, "Invalid data");
+ default:
+ break;
+ }
+
+ vc = vcedit_comments(state);
+
+ fields = rb_iv_get(self, "@fields");
+ rb_funcall(fields, rb_intern("clear"), 0);
+ add_to_fields = rb_intern("add_to_fields");
+
+ /* check whether all comments are well-formed */
+ for (i = 0; i < vc->comments; i++) {
+ char *ptr, *content = vc->user_comments[i];
+
+ ptr = strchr (content, '=');
+ if (!ptr || ptr == content) {
+ rb_funcall(fields, rb_intern("clear"), 0);
+ vcedit_state_unref(state);
+ rb_raise (eInvalidComment, "invalid comment - %s", content);
+ }
+
+ k = rb_str_new (content, ptr - content);
+ v = rb_str_new2 (ptr + 1);
+ rb_funcall(self, add_to_fields, 2, k, v);
+ }
+
+ vcedit_state_unref(state);
+ return fields;
+}
+
+/*
+ * call-seq:
+ * object.write_fields(comments) -> comments
+ *
+ * Writes the comments to the file. Do not call this directly.
+ */
+VALUE write_fields (VALUE self, VALUE comments) {
+ vcedit_state *state;
+ vorbis_comment *vc;
+ struct RArray *items, *comment;
+ VALUE filename;
+ int i, j;
+
+ filename = rb_iv_get(self, "@filename");
+ state = vcedit_state_new(StringValuePtr(filename));
+ if (!state)
+ rb_raise (rb_eNoMemError, "Out of Memory");
+
+ switch (vcedit_open (state)) {
+ case VCEDIT_ERR_OPEN:
+ vcedit_state_unref(state);
+ rb_raise (eOpen, "Cannot open file");
+ case VCEDIT_ERR_INVAL:
+ vcedit_state_unref(state);
+ rb_raise (eInvalidData, "Invalid data");
+ default:
+ break;
+ }
+
+ vc = vcedit_comments(state);
+ vorbis_comment_clear(vc);
+ vorbis_comment_init(vc);
+
+ items = RARRAY(comments);
+ for (i = 0; i < items->len; i++) {
+ comment = RARRAY(items->ptr[i]);
+ vorbis_comment_add_tag(vc, StringValuePtr(comment->ptr[0]), StringValuePtr(comment->ptr[1]));
+ }
+
+ switch (vcedit_write (state)) {
+ case VCEDIT_ERR_INVAL:
+ vcedit_state_unref(state);
+ rb_raise (eInvalidData, "Invalid data");
+ case VCEDIT_ERR_TMPFILE:
+ vcedit_state_unref(state);
+ rb_raise (eTempFile, "Cannot create temporary file");
+ case VCEDIT_ERR_REOPEN:
+ vcedit_state_unref(state);
+ rb_raise (eReopen, "Cannot reopen file");
+ default:
+ break;
+ }
+ vcedit_state_unref(state);
+
+ return comments;
+}
+
+void Init_vorbis_comment_ext(void) {
+ VALUE VorbisComment;
+ VorbisComment = rb_define_class("VorbisComment", rb_cObject);
+ rb_define_private_method(VorbisComment, "read_fields", read_fields, 0);
+ rb_define_private_method(VorbisComment, "write_fields", write_fields, 1);
+ eOpen = rb_define_class_under (VorbisComment, "OpenError", rb_eStandardError);
+ eInvalidData = rb_define_class_under (VorbisComment, "InvalidDataError", rb_eStandardError);
+ eInvalidComment = rb_define_class_under (VorbisComment, "InvalidCommentError", rb_eStandardError);
+ eTempFile = rb_define_class_under (VorbisComment, "TempFileError", rb_eStandardError);
+ eReopen = rb_define_class_under (VorbisComment, "ReopenError", rb_eStandardError);
+}

0 comments on commit df12a56

Please sign in to comment.
Something went wrong with that request. Please try again.