├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── docs ├── default │ └── fulldoc │ │ └── html │ │ └── css │ │ └── common.css └── taglib │ ├── aiff.rb │ ├── base.rb │ ├── flac.rb │ ├── id3v1.rb │ ├── id3v2.rb │ ├── mp4.rb │ ├── mpeg.rb │ ├── ogg.rb │ ├── riff.rb │ ├── vorbis.rb │ └── wav.rb ├── ext ├── extconf_common.rb ├── taglib_aiff │ ├── extconf.rb │ ├── taglib_aiff.i │ └── taglib_aiff_wrap.cxx ├── taglib_base │ ├── extconf.rb │ ├── includes.i │ ├── taglib_base.i │ └── taglib_base_wrap.cxx ├── taglib_flac │ ├── extconf.rb │ ├── taglib_flac.i │ └── taglib_flac_wrap.cxx ├── taglib_flac_picture │ ├── extconf.rb │ ├── includes.i │ ├── taglib_flac_picture.i │ └── taglib_flac_picture_wrap.cxx ├── taglib_id3v1 │ ├── extconf.rb │ ├── taglib_id3v1.i │ └── taglib_id3v1_wrap.cxx ├── taglib_id3v2 │ ├── extconf.rb │ ├── relativevolumeframe.i │ ├── taglib_id3v2.i │ └── taglib_id3v2_wrap.cxx ├── taglib_mp4 │ ├── extconf.rb │ ├── taglib_mp4.i │ └── taglib_mp4_wrap.cxx ├── taglib_mpeg │ ├── extconf.rb │ ├── taglib_mpeg.i │ └── taglib_mpeg_wrap.cxx ├── taglib_ogg │ ├── extconf.rb │ ├── taglib_ogg.i │ └── taglib_ogg_wrap.cxx ├── taglib_vorbis │ ├── extconf.rb │ ├── taglib_vorbis.i │ └── taglib_vorbis_wrap.cxx ├── taglib_wav │ ├── extconf.rb │ ├── taglib_wav.i │ └── taglib_wav_wrap.cxx ├── valgrind-suppressions.txt └── win.cmake ├── lib ├── taglib.rb └── taglib │ ├── aiff.rb │ ├── base.rb │ ├── flac.rb │ ├── id3v1.rb │ ├── id3v2.rb │ ├── mp4.rb │ ├── mpeg.rb │ ├── ogg.rb │ ├── version.rb │ ├── vorbis.rb │ └── wav.rb ├── taglib-ruby.gemspec ├── tasks ├── build.rb ├── docs_coverage.rake ├── ext.rake ├── gemspec_check.rake └── swig.rake └── test ├── aiff_examples_test.rb ├── aiff_file_test.rb ├── aiff_file_write_test.rb ├── base_test.rb ├── data ├── Makefile ├── add-relative-volume.cpp ├── aiff-sample.aiff ├── crash.mp3 ├── flac-create.cpp ├── flac.flac ├── flac_nopic.flac ├── get_picture_data.cpp ├── globe_east_540.jpg ├── globe_east_90.jpg ├── id3v1-create.cpp ├── id3v1.mp3 ├── mp4-create.cpp ├── mp4.m4a ├── relative-volume.mp3 ├── sample.mp3 ├── unicode.mp3 ├── vorbis-create.cpp ├── vorbis.oga ├── wav-create.cpp ├── wav-dump.cpp └── wav-sample.wav ├── file_test.rb ├── fileref_open_test.rb ├── fileref_properties_test.rb ├── fileref_write_test.rb ├── flac_file_test.rb ├── flac_file_write_test.rb ├── flac_picture_memory_test.rb ├── helper.rb ├── id3v1_genres_test.rb ├── id3v1_tag_test.rb ├── id3v2_frames_test.rb ├── id3v2_header_test.rb ├── id3v2_memory_test.rb ├── id3v2_relative_volume_test.rb ├── id3v2_tag_test.rb ├── id3v2_unicode_test.rb ├── id3v2_unknown_frames_test.rb ├── id3v2_write_test.rb ├── mp4_file_test.rb ├── mp4_file_write_test.rb ├── mp4_items_test.rb ├── mpeg_file_test.rb ├── tag_test.rb ├── unicode_filename_test.rb ├── vorbis_file_test.rb ├── vorbis_tag_test.rb ├── wav_examples_test.rb ├── wav_file_test.rb └── wav_file_write_test.rb /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: robinst 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | - '**.txt' 8 | - 'docs/**' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | - '**.txt' 13 | - 'docs/**' 14 | 15 | jobs: 16 | build: 17 | strategy: 18 | matrix: 19 | os: [ubuntu-20.04] 20 | ruby: [2.7, 3.2] 21 | 22 | runs-on: ${{ matrix.os }} 23 | 24 | env: 25 | MAKEFLAGS: -j2 26 | PLATFORM: x86_64-linux 27 | TAGLIB_VERSION: 2.0.1 28 | SWIG_DIR: .swig-v4.1.1 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: ${{ matrix.ruby }} 36 | 37 | - name: Cache SWIG v4.1.1 38 | id: cache-swig 39 | uses: actions/cache@v2 40 | with: 41 | path: ~/${{ env.SWIG_DIR }} 42 | key: swig-${{ matrix.os }}-v4.1.1 43 | 44 | - name: Install SWIG v4.1.1 45 | if: steps.cache-swig.outputs.cache-hit != 'true' 46 | run: | 47 | sudo apt install yodl 48 | git clone --depth 1 --branch v4.1.1 https://github.com/swig/swig.git 49 | cd swig 50 | ./autogen.sh 51 | ./configure --prefix=$HOME/$SWIG_DIR --with-ruby=$(which ruby) --without-alllang 52 | make && make install 53 | 54 | - name: Bundle Install 55 | run: | 56 | gem install bundler -v 2.4.22 57 | bundle config path vendor/bundle 58 | bundle install --jobs 4 --retry 3 59 | 60 | - name: Cache TagLib (C++) 61 | id: cache-taglib 62 | uses: actions/cache@v2 63 | with: 64 | path: | 65 | tmp/${{ env.PLATFORM }}/taglib-${{ env.TAGLIB_VERSION }} 66 | tmp/taglib-${{ env.TAGLIB_VERSION }}.tar.gz 67 | key: taglib-${{ matrix.os }}-v${{ env.TAGLIB_VERSION }}-cache.v3 68 | 69 | - name: Install TagLib (C++) 70 | if: steps.cache-taglib.outputs.cache-hit != 'true' 71 | run: bundle exec rake vendor 72 | 73 | - name: Generate SWIG Wrappers 74 | run: | 75 | touch ext/*/*.i 76 | export PATH=$HOME/$SWIG_DIR/bin:$PATH 77 | TAGLIB_DIR=$PWD/tmp/$PLATFORM/taglib-$TAGLIB_VERSION bundle exec rake swig 78 | echo 'Checking for changes (there should be none)' 79 | git diff --exit-code 80 | 81 | - name: Compile (taglib-ruby) 82 | run: TAGLIB_DIR=$PWD/tmp/$PLATFORM/taglib-$TAGLIB_VERSION bundle exec rake compile 83 | 84 | - name: Run Tests (taglib-ruby) 85 | run: bundle exec rake test 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Output from compiling extension 2 | *.o 3 | *.so 4 | mkmf.log 5 | Makefile 6 | *.dll 7 | *.bundle 8 | 9 | # Editor 10 | .*.swp 11 | .redcar 12 | .idea 13 | .vscode 14 | 15 | # Build 16 | /.yardoc 17 | /Gemfile.lock 18 | /doc 19 | /pkg 20 | /tmp 21 | /test/data/add-relative-volume 22 | /test/data/flac-create 23 | /test/data/id3v1-create 24 | /test/data/vorbis-create 25 | /test/data/mp4-create 26 | /test/data/wav-create 27 | /test/data/wav-dump 28 | /vendor 29 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | NewCops: disable 3 | Include: 4 | - '**/*.rb' 5 | - '**/*.arb' 6 | - '**/*.gemfile' 7 | - '**/*.gemspec' 8 | - '**/*.rake' 9 | - '**/.simplecov' 10 | - '**/Gemfile' 11 | - '**/Guardfile' 12 | - '**/Rakefile' 13 | Exclude: 14 | - 'docs/**/*' 15 | - 'tmp/**/*' 16 | 17 | Metrics/ClassLength: 18 | Enabled: false 19 | 20 | Metrics/BlockLength: 21 | Enabled: false 22 | 23 | Layout/LineLength: 24 | Enabled: false 25 | 26 | Style/EmptyMethod: 27 | Enabled: false 28 | 29 | Style/NumericLiterals: 30 | Enabled: false 31 | 32 | Style/ClassAndModuleChildren: 33 | Enabled: false 34 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | --markup-provider kramdown 3 | --charset UTF-8 4 | --template-path docs 5 | docs/**/*.rb 6 | - 7 | README.md 8 | CHANGELOG.md 9 | LICENSE.txt 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changes in Releases of taglib-ruby 2 | ================================== 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 7 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 8 | 9 | ## 2.0.0 10 | ### Changed 11 | - Regenerate SWIG wrapper code against TagLib 2.0.1. This breaks 12 | compatibility with TagLib 1.x. You will get a compiler error if you 13 | try to install taglib-ruby 2.x on a system that has TagLib 1.x. 14 | Please use taglib-ruby 1.x if your system has TagLib 1.x, and 15 | taglib-ruby 2.x if your system has TagLib 2.x. 16 | - The optional `strip_others` argument to `TagLib::MPEG::File#save` no 17 | longer takes a boolean value. It now uses the constants 18 | `TagLib::File::StripOthers` and `TagLib::File::StripNone`. 19 | 20 | ### Removed 21 | - `TagLib::MPEG::File#tag` has been removed because it no longer 22 | exists in TagLib 2.x. Please use `TagLib::MPEG::File#id3v2_tag` or 23 | `TagLib::MPEG::File#id3v1_tag`. 24 | 25 | ## 1.1.3 - 2022-12-29 26 | ### Changed 27 | - Fix warning `warning: undefining the allocator of T_DATA class 28 | swig_runtime_data` on Ruby 3.2 29 | - Upgraded to SWIG 4.1.1 30 | 31 | ## 1.1.2 - 2022-04-13 32 | ### Fixed 33 | - Fix UserTextIdentificationFrame's constructor so that overloaded 34 | variants with StringList arguments can be called (#107) 35 | 36 | ## 1.1.1 - 2022-04-12 37 | ### Changed 38 | - Fixed build time warnings with Ruby >= 2.7.0 (#85) 39 | - Upgraded to SWIG 4.0.2 40 | - Fixed running tests against TagLib 1.12 41 | 42 | ## 1.1.0 - 2021-01-20 43 | ### Added 44 | - Added support for CTOC and CHAP frames for ID3v2 45 | 46 | ## 1.0.1 - 2020-03-25 47 | 48 | ### Fixed 49 | - Fix segmentation fault with TagLib::FLAC picture lists (#91), thanks 50 | @jameswyper! 51 | 52 | ## 1.0.0 - 2020-01-07 53 | 54 | * Support for TagLib >= 1.11.1 (drop support for earlier versions) (#83) 55 | * This includes a lot of new APIs and some changed APIs, see 56 | `@since 1.0.0` in the docs 57 | * Stop using tainted strings to fix warnings with Ruby 2.7 (#86) 58 | 59 | ## 0.7.1 - 2015-12-28 60 | 61 | * Fix compile error during gem installation on Ruby 2.3 (MRI) (#67) 62 | 63 | ## 0.7.0 - 2014-08-21 64 | 65 | * Add support for TagLib::RIFF::AIFF (#52, by @tchev) 66 | * Add support for TagLib::RIFF::WAV (#57, by @tchev) 67 | * Associate filesystem encoding to filename strings 68 | * Allow CXX override during gem installation 69 | * Try to detect location of vendor/taglib on Heroku (#28) 70 | * Documentation updates 71 | 72 | ## 0.6.0 - 2013-04-26 73 | 74 | * Add support for TagLib::MP4 (#3, by @jacobvosmaer) 75 | * Add support for TagLib::ID3v2::Header (#19, by @kaethorn) 76 | * Support saving ID3v2.3 with TagLib::MPEG::File#save (#17) 77 | * Note that this requires at least TagLib 1.8, and due to 1.8.0 78 | having an incorrect version number in the headers, it currently 79 | requires master. See issue #17 for details. 80 | * Fix segfault when passing a non-String to a String argument 81 | * Documentation updates 82 | 83 | ## 0.5.2 - 2012-10-06 84 | 85 | * Fix memory bug with TagLib::MPEG::File#tag and TagLib::FLAC::File#tag 86 | which could cause crashes (#14) 87 | * Update TagLib of binary gem for Windows to 1.8 88 | 89 | ## 0.5.1 - 2012-06-16 90 | 91 | * Fix crashes (segfault) with nil arguments, e.g. with `tag.title = nil` 92 | * Document TagLib::MPEG::File#save and TagLib::MPEG::File#strip (#11) 93 | * Update TagLib of binary gem for Windows to 1.7.2 94 | 95 | ## 0.5.0 - 2012-04-15 96 | 97 | * Add support for FLAC 98 | * Fix problem in SWIG causing compilation error on MacRuby (#10) 99 | 100 | ## 0.4.0 - 2012-03-18 101 | 102 | * Pre-compiled binary gem for Windows (Ruby 1.9) with TagLib 1.7.1 103 | * Unicode filename support on Windows 104 | * Add `open` class method to `FileRef` and `File` classes (use it 105 | instead of `new` and `close`): 106 | 107 | ```ruby 108 | title = TagLib::FileRef.open("file.mp3") do |file| 109 | tag = file.tag 110 | tag.title 111 | end 112 | ``` 113 | 114 | ## 0.3.1 - 2012-01-22 115 | 116 | * Fix ObjectPreviouslyDeleted exception after calling 117 | TagLib::ID3v2::Tag#add_frame (#8) 118 | * Make installation under MacPorts work out of the box (#7) 119 | 120 | ## 0.3.0 - 2012-01-02 121 | 122 | * Add support for Ogg Vorbis 123 | * Add support for ID3v1 (#2) 124 | * Add #close to File classes for explicitly releasing resources 125 | * Fix compilation on Windows 126 | 127 | ## 0.2.1 - 2011-11-05 128 | 129 | * Fix compilation error due to missing typedef on some systems (#5) 130 | 131 | ## 0.2.0 - 2011-10-22 132 | 133 | * API documentation 134 | * Add support for: 135 | * TagLib::AudioProperties and TagLib::MPEG::Properties (#4) 136 | * TagLib::ID3v2::RelativeVolumeFrame 137 | 138 | ## 0.1.1 - 2011-09-17 139 | 140 | * Add installation instructions and clean up description 141 | 142 | ## 0.1.0 - 2011-09-17 143 | 144 | * Initial release 145 | * Coverage of the following API: 146 | * TagLib::FileRef 147 | * TagLib::MPEG::File 148 | * TagLib::ID3v2::Tag 149 | * TagLib::ID3v2::Frame and subclasses 150 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | source 'http://rubygems.org' 4 | 5 | # Use dependencies from .gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | # Guardfile for tests 4 | # More info at https://github.com/guard/guard#readme 5 | 6 | guard :test do 7 | watch(%r{^lib/(.+)\.so$}) { 'test' } 8 | watch('test/test_helper.rb') { 'test' } 9 | watch(%r{^test/.+_test\.rb$}) 10 | end 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # taglib-ruby 2 | 3 | Ruby interface for the [TagLib C++ library][taglib], for reading and 4 | writing meta-data (tags) of many audio formats. 5 | 6 | In contrast to other libraries, this one wraps the full C++ API, not 7 | only the minimal C API. This means that all tag data can be accessed, 8 | e.g. cover art of ID3v2 or custom fields of Ogg Vorbis comments. 9 | 10 | `taglib-ruby` currently supports the following: 11 | 12 | * Reading/writing common tag data of all formats that TagLib supports 13 | * Reading/writing ID3v1 and ID3v2 including ID3v2.4 and Unicode 14 | * Reading/writing Ogg Vorbis comments 15 | * Reading/writing MP4 tags (.m4a) 16 | * Reading audio properties (e.g. bitrate) of the above formats 17 | 18 | Contributions for more coverage of the library are very welcome. 19 | 20 | [![Gem version][gem-img]][gem-link] 21 | [![ci](https://github.com/robinst/taglib-ruby/workflows/ci/badge.svg)](https://github.com/robinst/taglib-ruby/actions?query=workflow%3Aci) 22 | 23 | ## Installation 24 | 25 | Before you install the gem, make sure to have [taglib 1.11.1 or higher][taglib] 26 | installed with header files (and a C++ compiler of course): 27 | 28 | * Debian/Ubuntu: `sudo apt-get install libtag1-dev` 29 | * Fedora/RHEL: `sudo dnf install taglib-devel` 30 | * Brew: `brew install taglib` 31 | * MacPorts: `sudo port install taglib` 32 | 33 | Then install the latest taglib-ruby 1.x by running: 34 | 35 | gem install taglib-ruby --version '< 2' 36 | 37 | If you are using TagLib 2.0.1 or higher, you need to install taglib-ruby 2.x instead: 38 | 39 | gem install taglib-ruby --version '>= 2' 40 | 41 | ### MacOS 42 | 43 | Depending on your brew setup, TagLib might be installed in different locations, 44 | which makes it hard for taglib-ruby to find it. To get the library location, run: 45 | 46 | $ brew info taglib 47 | taglib: stable 1.13 (bottled), HEAD 48 | Audio metadata library 49 | https://taglib.org/ 50 | /opt/homebrew/Cellar/taglib/1.13 (122 files, 1.6MB) * 51 | ... 52 | 53 | Note the line with the path at the end. Provide that using the `TAGLIB_DIR` 54 | environment variable when installing, like this: 55 | 56 | TAGLIB_DIR=/opt/homebrew/Cellar/taglib/1.13 gem install taglib-ruby 57 | 58 | If you're using bundler, like this: 59 | 60 | TAGLIB_DIR=/opt/homebrew/Cellar/taglib/1.13 bundle install 61 | 62 | Another problem might be that `clang++` doesn't work with a specific version 63 | of TagLib. In that case, try compiling taglib-ruby's C++ extensions with a 64 | different compiler: 65 | 66 | TAGLIB_RUBY_CXX=g++-4.2 gem install taglib-ruby 67 | 68 | ## Usage 69 | 70 | Complete API documentation can be found on 71 | [rubydoc.info](http://rubydoc.info/gems/taglib-ruby/frames). 72 | 73 | Begin with the `TagLib` namespace. 74 | 75 | ## Release Notes 76 | 77 | See [CHANGELOG.md](CHANGELOG.md). 78 | 79 | ## Contributing 80 | 81 | ### Dependencies 82 | 83 | Fedora: 84 | 85 | sudo dnf install taglib-devel ruby-devel gcc-c++ redhat-rpm-config swig 86 | 87 | ### Building 88 | 89 | Install dependencies (uses bundler, install it via `gem install bundler` 90 | if you don't have it): 91 | 92 | bundle install 93 | 94 | Regenerate SWIG wrappers if you made changes in `.i` files (use version 3.0.7 of 95 | SWIG - 3.0.8 through 3.0.12 will not work): 96 | 97 | rake swig 98 | 99 | Force regeneration of all SWIG wrappers: 100 | 101 | touch ext/*/*.i 102 | rake swig 103 | 104 | Compile extensions: 105 | 106 | rake clean compile 107 | 108 | Run tests: 109 | 110 | rake test 111 | 112 | Run irb with library: 113 | 114 | irb -Ilib -rtaglib 115 | 116 | Build and install gem into system gems: 117 | 118 | rake install 119 | 120 | Build a specific version of Taglib: 121 | 122 | PLATFORM=x86_64-linux TAGLIB_VERSION=1.11.1 rake vendor 123 | 124 | The above command will automatically download Taglib 1.11.1, build it and 125 | install it in `tmp/x86_64-linux/taglib-1.11.1`. 126 | 127 | The `swig`, `compile` and `test` tasks can then be executed against that specific 128 | version of Taglib by setting the `TAGLIB_DIR` environment variable to 129 | `$PWD/tmp/x86_64-linux/taglib-1.11.1` (it is assumed that taglib headers are 130 | located at `$TAGLIB_DIR/include` and taglib libraries at `$TAGLIB_DIR/lib`). 131 | 132 | To do everything in one command: 133 | 134 | PLATFORM=x86_64-linux TAGLIB_VERSION=1.11.1 TAGLIB_DIR=$PWD/tmp/x86_64-linux/taglib-1.11.1 rake vendor compile test 135 | 136 | ### Workflow 137 | 138 | * Check out the latest `main` branch to make sure the feature hasn't been 139 | implemented or the bug hasn't been fixed yet. 140 | * Check out the issue tracker to make sure someone hasn't already 141 | requested it and/or contributed it. 142 | * Fork the project. 143 | * Start a feature/bugfix branch from `main`. 144 | * Commit and push until you are happy with your contribution. 145 | * Make sure to add tests for it. This is important so that I don't break it 146 | in a future version unintentionally. 147 | * Run `rubocop` locally to lint your changes and fix the issues. Please refer to 148 | [.rubocop.yml](.rubocop.yml) for the list of relaxed rules. Try to keep the 149 | liniting offenses to minimum. Preferably, first run `rubocop` on your fork to 150 | have a general idea of the existing linting offenses before writing new code. 151 | * Please try not to mess with the Rakefile, version, or history. If you 152 | want to have your own version, or is otherwise necessary, that is 153 | fine, but please isolate to its own commit so I can cherry-pick around 154 | it. 155 | 156 | ## License 157 | 158 | Copyright (c) 2010-2022 Robin Stocker and others, see Git history. 159 | 160 | `taglib-ruby` is distributed under the MIT License, see 161 | [LICENSE.txt](LICENSE.txt) for details. 162 | 163 | In the binary gem for Windows, a compiled [TagLib][taglib] is bundled as 164 | a DLL. TagLib is distributed under the GNU Lesser General Public License 165 | version 2.1 (LGPL) and Mozilla Public License (MPL). 166 | 167 | [taglib]: http://taglib.github.io/ 168 | [gem-img]: https://badge.fury.io/rb/taglib-ruby.svg 169 | [gem-link]: https://rubygems.org/gems/taglib-ruby 170 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | begin 5 | Bundler.setup(:default, :development) 6 | rescue Bundler::BundlerError => e 7 | warn e.message 8 | warn 'Run `bundle install` to install missing gems' 9 | exit e.status_code 10 | end 11 | 12 | require 'rake/testtask' 13 | Rake::TestTask.new(:test) do |test| 14 | test.libs << 'test' 15 | test.pattern = 'test/**/*_test.rb' 16 | end 17 | 18 | task default: %i[compile test] 19 | 20 | require 'yard' 21 | YARD::Rake::YardocTask.new do |t| 22 | version = TagLib::Version::STRING 23 | t.options = ['--title', "taglib-ruby #{version}"] 24 | end 25 | 26 | $gemspec = Bundler::GemHelper.gemspec 27 | 28 | import 'tasks/docs_coverage.rake' 29 | import 'tasks/ext.rake' 30 | import 'tasks/gemspec_check.rake' 31 | 32 | # When importing swig.rake, the *_wrap.cxx files depend on being generated 33 | # by Swig. Since the ExtensionTasks depend on the *_wrap.cxx files, 34 | # compiling the extensions will trigger Swig, which is not desired as 35 | # those files have already been generated and there's no reason to make 36 | # Swig a variable of the CI. The environment variable can be set to 37 | # prevent running swig. 38 | import 'tasks/swig.rake' unless ENV['SKIP_SWIG'] == 'true' 39 | -------------------------------------------------------------------------------- /docs/default/fulldoc/html/css/common.css: -------------------------------------------------------------------------------- 1 | .showSource { display: none; } 2 | -------------------------------------------------------------------------------- /docs/taglib/aiff.rb: -------------------------------------------------------------------------------- 1 | # @since 0.7.0 2 | module TagLib::RIFF::AIFF 3 | # The file class for `.aiff` files. 4 | # 5 | # @example Reading the title 6 | # title = TagLib::RIFF::AIFF::File.open("sample.aiff") do |file| 7 | # file.tag.title 8 | # end 9 | # 10 | # @example Reading AIFF-specific audio properties 11 | # TagLib::RIFF::AIFF::File.open("sample.aiff") do |file| 12 | # file.audio_properties.sample_width #=> 16 13 | # end 14 | # 15 | # @example Saving ID3v2 cover-art to disk 16 | # TagLib::RIFF::AIFF::File.open("sample2.aif") do |file| 17 | # id3v2_tag = file.tag 18 | # cover = id3v2_tag.frame_list('APIC').first 19 | # ext = cover.mime_type.rpartition('/')[2] 20 | # File.open("cover-art.#{ext}", "wb") { |f| f.write cover.picture } 21 | # end 22 | # 23 | # @see ID3v2::Tag ID3v2 examples. 24 | # 25 | class File < TagLib::File 26 | # {include:::TagLib::FileRef.open} 27 | # 28 | # @param (see #initialize) 29 | # @yield [file] the {File} object, as obtained by {#initialize} 30 | # @return the return value of the block 31 | # 32 | def self.open(filename, read_properties=true) 33 | end 34 | 35 | # Load an AIFF file. 36 | # 37 | # @param [String] filename 38 | # @param [Boolean] read_properties if audio properties should be 39 | # read 40 | def initialize(filename, read_properties=true) 41 | end 42 | 43 | # Returns the ID3v2 tag. 44 | # 45 | # @return [TagLib::ID3v2::Tag] 46 | def tag 47 | end 48 | 49 | # Returns audio properties. 50 | # 51 | # @return [TagLib::RIFF::AIFF::Properties] 52 | def audio_properties 53 | end 54 | 55 | # @return [Boolean] Whether or not the file on disk actually has an ID3v2 tag. 56 | # 57 | # @since 1.0.0 58 | def id3v2_tag? 59 | end 60 | end 61 | 62 | class Properties < TagLib::AudioProperties 63 | # @return [Integer] Number of bits per audio sample. 64 | # 65 | # @since 1.0.0 66 | attr_reader :bits_per_sample 67 | 68 | # @return [Integer] Number of sample frames. 69 | # 70 | # @since 1.0.0 71 | attr_reader :sample_frames 72 | 73 | # @return [String] The compression type of the AIFF-C file. 74 | # For example, "NONE" for not compressed, "ACE2" for ACE 2-to-1. 75 | # If the file is in AIFF format, always returns an empty string. 76 | # 77 | # @since 1.0.0 78 | attr_reader :compression_type 79 | 80 | # @return [String] Returns the concrete compression name of the AIFF-C file. 81 | # If the file is in AIFF format, always returns an empty string. 82 | # 83 | # @since 1.0.0 84 | attr_reader :compression_name 85 | 86 | # @return [Boolean] True if the file is in AIFF-C format, false if AIFF format. 87 | # 88 | # @since 1.0.0 89 | def aiff_c? 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /docs/taglib/base.rb: -------------------------------------------------------------------------------- 1 | # This is the top-level module of taglib-ruby. 2 | # 3 | # Where to find what: 4 | # 5 | # * Reading/writing basic tag and audio properties without having to 6 | # know the tagging format: {TagLib::FileRef} 7 | # * Reading properties of MPEG files: {TagLib::MPEG::File} 8 | # * Reading/writing ID3v2 tags: {TagLib::MPEG::File}, {TagLib::RIFF::AIFF::File} and 9 | # {TagLib::ID3v2::Tag} 10 | # * Reading/writing Ogg Vorbis tags: {TagLib::Ogg::Vorbis::File} 11 | # * Reading/writing FLAC tags: {TagLib::FLAC::File} 12 | # 13 | # ## String Encodings 14 | # 15 | # Sometimes, it is necessary to specify which encoding should be used to 16 | # store strings in tags. For this, the following constants are defined: 17 | # 18 | # * `TagLib::String::Latin1` 19 | # * `TagLib::String::UTF16` 20 | # * `TagLib::String::UTF16BE` 21 | # * `TagLib::String::UTF8` 22 | # * `TagLib::String::UTF16LE` 23 | # 24 | # For ID3v2 frames, you can also set a default text encoding globally 25 | # using the {TagLib::ID3v2::FrameFactory}. 26 | # 27 | # ## Modifying attributes 28 | # 29 | # Mutable Ruby types (String, Array) returned by TagLib cannot be modified in-place. 30 | # 31 | # @example Modifying an attribute in-place does not work 32 | # tag.title = 'Title' 33 | # tag.title 34 | # # => "Title" 35 | # tag.title << ' of the song' 36 | # # => "Title of the song" 37 | # tag.title 38 | # # => "Title" 39 | # 40 | # @example You need to replace the existing attribute value 41 | # tag.title = 'Title' 42 | # tag.title 43 | # # => "Title" 44 | # tag.title = 'Title of the song' 45 | # tag.title 46 | # # => "Title of the song" 47 | module TagLib 48 | # Major version of TagLib the extensions were compiled against 49 | # (major.minor.patch). Note that the value is not actually 0, but 50 | # depends on the version of the installed library. 51 | TAGLIB_MAJOR_VERSION = 0 52 | 53 | # Minor version of TagLib the extensions were compiled against 54 | # (major.minor.patch). Note that the value is not actually 0, but 55 | # depends on the version of the installed library. 56 | TAGLIB_MINOR_VERSION = 0 57 | 58 | # Patch version of TagLib the extensions were compiled against 59 | # (major.minor.patch). Note that the value is not actually 0, but 60 | # depends on the version of the installed library. 61 | TAGLIB_PATCH_VERSION = 0 62 | 63 | # This class allows to read basic tagging and audio properties from 64 | # files, without having to know what the file type is. Thus, it works 65 | # for all tagging formats that taglib supports, but only provides a 66 | # minimal API. 67 | # 68 | # Should you need more, use the file type specific classes, see 69 | # subclasses of {TagLib::File}. 70 | # 71 | # @example Reading tags 72 | # TagLib::FileRef.open("foo.flac") do |file| 73 | # unless file.null? 74 | # tag = file.tag 75 | # puts tag.artist 76 | # puts tag.title 77 | # end 78 | # end 79 | # 80 | # @example Reading audio properties 81 | # TagLib::FileRef.open("bar.oga") do |file| 82 | # unless file.null? 83 | # prop = file.audio_properties 84 | # puts prop.length 85 | # puts prop.bitrate 86 | # end 87 | # end 88 | # 89 | class FileRef 90 | # Creates a new file and passes it to the provided block, 91 | # closing the file automatically at the end of the block. 92 | # 93 | # Note that after the block is done, the file is closed and 94 | # all memory is released for objects read from the file 95 | # (basically everything from the `TagLib` namespace). 96 | # 97 | # Using `open` is preferable to using `new` and then 98 | # manually `close`. 99 | # 100 | # @example Reading a title 101 | # title = TagLib::FileRef.open("file.oga") do |file| 102 | # tag = file.tag 103 | # tag.title 104 | # end 105 | # 106 | # @param (see #initialize) 107 | # @yield [file] the {FileRef} object, as obtained by {#initialize} 108 | # @return the return value of the block 109 | # 110 | # @since 0.4.0 111 | def self.open(filename, read_audio_properties=true, 112 | audio_properties_style=TagLib::AudioProperties::Average) 113 | end 114 | 115 | # Create a FileRef from a file name. 116 | # 117 | # @param [String] filename 118 | # @param [Boolean] read_audio_properties 119 | # true if audio properties should be read 120 | # @param [TagLib::AudioProperties constants] audio_properties_style 121 | # how accurately the audio properties should be read, e.g. 122 | # {TagLib::AudioProperties::Average} 123 | def initialize(filename, read_audio_properties=true, 124 | audio_properties_style=TagLib::AudioProperties::Average) 125 | end 126 | 127 | # Gets the audio properties. Before accessing it, check if there 128 | # were problems reading the file using {#null?}. If the audio 129 | # properties are accessed anyway, a warning will be printed and it 130 | # will return nil. 131 | # 132 | # @return [TagLib::AudioProperties] the audio properties 133 | def audio_properties 134 | end 135 | 136 | # @return [Boolean] if the file is null (i.e. it could not be read) 137 | def null? 138 | end 139 | 140 | # Saves the file 141 | # 142 | # @return [Boolean] whether saving was successful 143 | def save 144 | end 145 | 146 | # Gets the tag. Before accessing it, check if there were problems 147 | # reading the file using {#null?}. If the tag is accessed anyway, a 148 | # warning will be printed and it will return nil. 149 | # 150 | # @return [TagLib::Tag] the tag, or nil 151 | def tag 152 | end 153 | 154 | # Closes the file and releases all objects that were read from the 155 | # file. 156 | # 157 | # @see TagLib::File#close 158 | # 159 | # @return [void] 160 | def close 161 | end 162 | end 163 | 164 | # @abstract Base class for files, see subclasses. 165 | class File 166 | # Save the file and the associated tags. 167 | # 168 | # See subclasses, as some provide more control over what is saved. 169 | # 170 | # @return [Boolean] whether saving was successful 171 | def save 172 | end 173 | 174 | # Closes the file and releases all objects that were read from the 175 | # file (basically everything from the TagLib namespace). 176 | # 177 | # After this method has been called, no other methods on this object 178 | # may be called. So it's a good idea to always use it like this: 179 | # 180 | # file = TagLib::MPEG::File.new("file.mp3") 181 | # # ... 182 | # file.close 183 | # file = nil 184 | # 185 | # This method should always be called as soon as you're finished 186 | # with a file. Otherwise the file will only be closed when GC is 187 | # run, which may be much later. On Windows, this is especially 188 | # important as the file is locked until it is closed. 189 | # 190 | # As a better alternative to this, use the `open` class method: 191 | # 192 | # TagLib::MPEG::File.open("file.mp3") do |file| 193 | # # ... 194 | # end 195 | # 196 | # @return [void] 197 | def close 198 | end 199 | end 200 | 201 | # @abstract Base class for tags. 202 | # 203 | # This is a unified view which provides basic tag information, which 204 | # is common in all tag formats. See subclasses for functionality that 205 | # goes beyond this interface. 206 | class Tag 207 | # @return [String] the album 208 | # @return [nil] if not present 209 | attr_accessor :album 210 | 211 | # @return [String] the artist/interpret 212 | # @return [nil] if not present 213 | attr_accessor :artist 214 | 215 | # @return [String] the comment 216 | # @return [nil] if not present 217 | attr_accessor :comment 218 | 219 | # @return [String] the genre 220 | # @return [nil] if not present 221 | attr_accessor :genre 222 | 223 | # @return [String] the title 224 | # @return [nil] if not present 225 | attr_accessor :title 226 | 227 | # @return [Integer] the track number 228 | # @return [0] if not present 229 | attr_accessor :track 230 | 231 | # @return [Integer] the year 232 | # @return [0] if not present 233 | attr_accessor :year 234 | 235 | # @return [Boolean] 236 | def empty?; end 237 | end 238 | 239 | # @abstract Base class for audio properties. 240 | class AudioProperties 241 | Fast = 0 242 | Average = 1 243 | Accurate = 2 244 | 245 | # @return [Integer] length of the file in seconds 246 | # 247 | # @since 1.0.0 248 | attr_reader :length_in_seconds 249 | 250 | # @return [Integer] length of the file in milliseconds 251 | # 252 | # @since 1.0.0 253 | attr_reader :length_in_milliseconds 254 | 255 | # @return [Integer] bit rate in kb/s (kilobit per second) 256 | attr_reader :bitrate 257 | 258 | # @return [Integer] sample rate in Hz 259 | attr_reader :sample_rate 260 | 261 | # @return [Integer] number of channels 262 | attr_reader :channels 263 | end 264 | end 265 | -------------------------------------------------------------------------------- /docs/taglib/flac.rb: -------------------------------------------------------------------------------- 1 | # @since 0.5.0 2 | module TagLib::FLAC 3 | # The file class for `.flac` files. 4 | # 5 | # Note that Xiph comments is the primary tagging format for FLAC files. When 6 | # saving a file, if there's not yet a Xiph comment, it is created from 7 | # existing ID3 tags. ID3 tags will be updated if they exist, but not created 8 | # automatically. 9 | # 10 | # @example Reading Xiph comments 11 | # TagLib::FLAC::File.open("file.flac") do |file| 12 | # tag = file.xiph_comment 13 | # puts tag.title 14 | # fields = tag.field_list_map 15 | # puts fields['DATE'] 16 | # end 17 | # 18 | # @example Adding a picture 19 | # TagLib::FLAC::File.open("file.flac") do |file| 20 | # pic = TagLib::FLAC::Picture.new 21 | # pic.type = TagLib::FLAC::Picture::FrontCover 22 | # pic.mime_type = "image/jpeg" 23 | # pic.description = "desc" 24 | # pic.width = 90 25 | # pic.height = 90 26 | # pic.data = File.open("cover.jpg", 'rb') { |f| f.read } 27 | # 28 | # file.add_picture(pic) 29 | # file.save 30 | # end 31 | class File < TagLib::File 32 | NoTags = 0x0000 33 | XiphComment = 0x0001 34 | ID3v1 = 0x0002 35 | ID3v2 = 0x0004 36 | AllTags = 0xffff 37 | 38 | # {include:::TagLib::FileRef.open} 39 | # 40 | # @param (see #initialize) 41 | # @yield [file] the {File} object, as obtained by {#initialize} 42 | # @return the return value of the block 43 | def self.open(filename, read_properties=true) 44 | end 45 | 46 | # Load a FLAC file. 47 | # 48 | # @param [String] filename 49 | # @param [Boolean] read_properties if audio properties should be 50 | # read 51 | def initialize(filename, read_properties=true) 52 | end 53 | 54 | # Returns the union of the Xiph comment, ID3v1 and ID3v2 tag. 55 | # 56 | # @return [TagLib::Tag] 57 | def tag 58 | end 59 | 60 | # Returns the Xiph comment tag. 61 | # 62 | # @return [TagLib::Ogg::XiphComment] 63 | def xiph_comment 64 | end 65 | 66 | # Returns the ID3v1 tag. 67 | # 68 | # @return [TagLib::ID3v1::Tag] 69 | def id3v1_tag 70 | end 71 | 72 | # Returns the ID3v2 tag. 73 | # 74 | # @return [TagLib::ID3v2::Tag] 75 | def id3v2_tag 76 | end 77 | 78 | # Returns audio properties. 79 | # 80 | # @return [TagLib::FLAC::Properties] 81 | def audio_properties 82 | end 83 | 84 | # Returns an array of the pictures attached to the file. 85 | # 86 | # @return [Array] 87 | def picture_list 88 | end 89 | 90 | # Remove the specified picture. 91 | # 92 | # @param [TagLib::FLAC::Picture] picture 93 | # 94 | # @since 1.0.0 95 | def remove_picture(picture) 96 | end 97 | 98 | # Remove all pictures. 99 | # 100 | # @return [void] 101 | def remove_pictures 102 | end 103 | 104 | # Add a picture to the file. 105 | # 106 | # @param [TagLib::FLAC::Picture] picture 107 | # @return [void] 108 | def add_picture(picture) 109 | end 110 | 111 | # Remove the tags matching the specified OR-ed types. 112 | # 113 | # @param [int] tags The types of tags to remove. 114 | # @return [void] 115 | # 116 | # @since 1.0.0 117 | def strip(tags=TagLib::FLAC::File::AllTags) 118 | end 119 | 120 | # @return [Boolean] Whether or not the file on disk actually has a XiphComment. 121 | # 122 | # @since 1.0.0 123 | def xiph_comment? 124 | end 125 | 126 | # @return [Boolean] Whether or not the file on disk actually has an ID3v1 tag. 127 | # 128 | # @since 1.0.0 129 | def id3v1_tag? 130 | end 131 | 132 | # @return [Boolean] Whether or not the file on disk actually has an ID3v2 tag. 133 | # 134 | # @since 1.0.0 135 | def id3v2_tag? 136 | end 137 | end 138 | 139 | # FLAC audio properties. 140 | class Properties < TagLib::AudioProperties 141 | # @return [Integer] Number of bits per audio sample. 142 | # 143 | # @since 1.0.0 144 | attr_reader :bits_per_sample 145 | 146 | # @return [Integer] Number of sample frames. 147 | # 148 | # @since 1.0.0 149 | attr_reader :sample_frames 150 | 151 | # @return [binary String] MD5 signature of uncompressed audio stream 152 | # (binary data) 153 | attr_reader :signature 154 | end 155 | 156 | # FLAC picture, e.g. for attaching a cover image to a file. 157 | # 158 | # The constants in this class are used for the {#type} attribute. 159 | class Picture 160 | # Other 161 | Other = 0x00 162 | # 32x32 file icon (PNG only) 163 | FileIcon = 0x01 164 | OtherFileIcon = 0x02 165 | FrontCover = 0x03 166 | BackCover = 0x04 167 | LeafletPage = 0x05 168 | Media = 0x06 169 | LeadArtist = 0x07 170 | Artist = 0x08 171 | Conductor = 0x09 172 | Band = 0x0A 173 | Composer = 0x0B 174 | Lyricist = 0x0C 175 | RecordingLocation = 0x0D 176 | DuringRecording = 0x0E 177 | DuringPerformance = 0x0F 178 | MovieScreenCapture = 0x10 179 | ColouredFish = 0x11 180 | Illustration = 0x12 181 | BandLogo = 0x13 182 | PublisherLogo = 0x14 183 | 184 | def initialize 185 | end 186 | 187 | # Type of the picture, see constants. 188 | # @return [Picture constant] 189 | attr_accessor :type 190 | 191 | # MIME type (e.g. `"image/png"`) 192 | # @return [String] 193 | attr_accessor :mime_type 194 | 195 | # @return [String] 196 | attr_accessor :description 197 | 198 | # Picture width in pixels 199 | # @return [Integer] 200 | attr_accessor :width 201 | 202 | # Picture height in pixels 203 | # @return [Integer] 204 | attr_accessor :height 205 | 206 | # Color depth (in bits-per-pixel) 207 | # @return [Integer] 208 | attr_accessor :color_depth 209 | 210 | # Number of colors (for indexed images) 211 | # @return [Integer] 212 | attr_accessor :num_colors 213 | 214 | # Picture data 215 | # 216 | # Be sure to use a binary string when setting this attribute. In 217 | # Ruby 1.9, this means reading from a file with `"b"` mode to get a 218 | # string with encoding `BINARY` / `ASCII-8BIT`. 219 | # 220 | # @return [binary String] 221 | attr_accessor :data 222 | 223 | # Parse the picture data in the FLAC picture block format. 224 | # @return [Boolean] True if the data has been parsed successfully, false otherwise. 225 | # 226 | # @since 1.0.0 227 | def parse(rawdata) 228 | end 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /docs/taglib/id3v1.rb: -------------------------------------------------------------------------------- 1 | module TagLib::ID3v1 2 | # An ID3v1 tag. 3 | class Tag < TagLib::Tag 4 | # @return [Integer] the genre as a number 5 | # @return [255] if not present 6 | # 7 | # @since 1.0.0 8 | attr_accessor :genre_number 9 | end 10 | 11 | # @return [Array] the ID3v1 genre list. 12 | # 13 | # @since 1.0.0 14 | def genre_list 15 | end 16 | 17 | # @return [Map] the map associating a genre to its index. 18 | # 19 | # @since 1.0.0 20 | def genre_map 21 | end 22 | 23 | # @return [String] the name of genre at `index` in the ID3v1 genre list. 24 | # 25 | # @since 1.0.0 26 | def genre(index) 27 | end 28 | 29 | # @return [String] the genre index for the (case sensitive) genre `name`. 30 | # 31 | # @since 1.0.0 32 | def genre_index(name) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /docs/taglib/mpeg.rb: -------------------------------------------------------------------------------- 1 | module TagLib::MPEG 2 | # The file class for `.mp3` and other MPEG files. 3 | # 4 | # @example Reading a title 5 | # title = TagLib::MPEG::File.open("file.mp3") do |file| 6 | # tag = file.tag 7 | # tag.title 8 | # end 9 | # 10 | class File < TagLib::File 11 | NoTags = 0x0000 12 | ID3v1 = 0x0001 13 | ID3v2 = 0x0002 14 | APE = 0x0004 15 | AllTags = 0xffff 16 | 17 | # {include:::TagLib::FileRef.open} 18 | # 19 | # @param (see #initialize) 20 | # @yield [file] the {File} object, as obtained by {#initialize} 21 | # @return the return value of the block 22 | # 23 | # @since 0.4.0 24 | def self.open(filename, read_properties=true) 25 | end 26 | 27 | # Load an MPEG file. 28 | # 29 | # @param [String] filename 30 | # @param [Boolean] read_properties if audio properties should be 31 | # read 32 | def initialize(filename, read_properties=true) 33 | end 34 | 35 | # Returns the ID3v1 tag. 36 | # 37 | # @param create if a new tag should be created when none exists 38 | # @return [TagLib::ID3v1::Tag] 39 | # @return [nil] if not present 40 | def id3v1_tag(create=false) 41 | end 42 | 43 | # Returns the ID3v2 tag. 44 | # 45 | # @param create if a new tag should be created when none exists 46 | # @return [TagLib::ID3v2::Tag] 47 | # @return [nil] if not present 48 | def id3v2_tag(create=false) 49 | end 50 | 51 | # Returns audio properties. 52 | # 53 | # @return [TagLib::MPEG::Properties] 54 | def audio_properties 55 | end 56 | 57 | # Save the file and the associated tags. 58 | # 59 | # @overload save(tags=TagLib::MPEG::File::AllTags, strip_others=true) 60 | # 61 | # @param [Integer] tags 62 | # The tag types to save (see constants), e.g. 63 | # {TagLib::MPEG::File::ID3v2}. To specify more than one tag type, 64 | # or them together using `|`, e.g. 65 | # `TagLib::MPEG::File::ID3v1 | TagLib::MPEG::File::ID3v2`. 66 | # @param [Boolean] strip_others 67 | # true if tag types other than the specified ones should be 68 | # stripped from the file 69 | # 70 | # @overload save(tags, strip_others, id3v2_version) 71 | # 72 | # @param [Integer] id3v2_version 73 | # 3 if the saved ID3v2 tag should be in version ID3v2.3, or 4 if 74 | # it should use ID3v2.4 75 | # 76 | # This overload can only be called if the extension was compiled 77 | # against TagLib >= 1.8. Otherwise it will raise an ArgumentError. 78 | # So either check the version using {TagLib::TAGLIB_MAJOR_VERSION} 79 | # and {TagLib::TAGLIB_MINOR_VERSION} or be prepared to rescue the 80 | # ArgumentError. 81 | # 82 | # @return [Boolean] whether saving was successful 83 | def save(tags=TagLib::MPEG::File::AllTags, strip_others=TagLib::File::StripOthers) 84 | end 85 | 86 | # Strip the specified tags from the file. Note that this directly 87 | # updates the file, a call to save afterwards is not necessary 88 | # (closing the file is necessary as always, though). 89 | # 90 | # @param [Integer] tags 91 | # The tag types to strip (see constants), e.g. 92 | # {TagLib::MPEG::File::ID3v2}. To specify more than one tag type, 93 | # or them together using `|`, e.g. 94 | # `TagLib::MPEG::File::ID3v1 | TagLib::MPEG::File::ID3v2`. 95 | # @return [Boolean] whether stripping was successful 96 | def strip(tags=TagLib::MPEG::File::AllTags) 97 | end 98 | 99 | # @return [Boolean] Whether or not the file on disk actually has an ID3v1 tag. 100 | # 101 | # @since 1.0.0 102 | def id3v1_tag? 103 | end 104 | 105 | # @return [Boolean] Whether or not the file on disk actually has an ID3v2 tag. 106 | # 107 | # @since 1.0.0 108 | def id3v2_tag? 109 | end 110 | 111 | # @return [Boolean] Whether or not the file on disk actually has an APE tag. 112 | # 113 | # @since 1.0.0 114 | def ape_tag? 115 | end 116 | end 117 | 118 | # Audio properties for MPEG files. 119 | class Properties < TagLib::AudioProperties 120 | # @return [TagLib::MPEG::XingHeader] Xing header 121 | # @return [nil] if not present 122 | def xing_header 123 | end 124 | 125 | # @return [TagLib::MPEG::Header constant] MPEG version, 126 | # e.g. {TagLib::MPEG::Header::Version1} 127 | def version 128 | end 129 | 130 | # @return [Integer] MPEG layer (1-3) 131 | def layer 132 | end 133 | 134 | # @return [true] if MPEG protection bit is set 135 | def protection_enabled 136 | end 137 | 138 | # @return [TagLib::MPEG::Header constant] channel mode, 139 | # e.g. {TagLib::MPEG::Header::JointStereo} 140 | def channel_mode 141 | end 142 | 143 | # @return [true] if copyrighted bit is set 144 | def copyrighted? 145 | end 146 | 147 | # @return [true] if original bit is set 148 | def original? 149 | end 150 | end 151 | 152 | # MPEG header. 153 | class Header 154 | Version1 = 0 155 | Version2 = 1 156 | Version2_5 = 2 157 | 158 | Stereo = 0 159 | JointStereo = 1 160 | DualChannel = 2 161 | SingleChannel = 3 162 | end 163 | 164 | # Xing VBR header. 165 | class XingHeader 166 | Invalid = 0 167 | Xing = 1 168 | VBRI = 2 169 | 170 | # @return [true] if a valid Xing header is present 171 | def valid? 172 | end 173 | 174 | # @return [Integer] total number of frames 175 | def total_frames 176 | end 177 | 178 | # @return [Integer] total size of stream in bytes 179 | def total_size 180 | end 181 | 182 | # @return [Integer] the type of the VBR header. 183 | # 184 | # @since 1.0.0 185 | def type 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /docs/taglib/ogg.rb: -------------------------------------------------------------------------------- 1 | module TagLib::Ogg 2 | # @abstract Base class for Ogg files, see subclasses. 3 | class File < TagLib::File 4 | end 5 | 6 | # Xiph comments (aka VorbisComment), a metadata format used for Ogg 7 | # Vorbis and other codecs. 8 | # 9 | # A Xiph comment is structured as a set of fields. Each field has a 10 | # name and a value. Multiple fields with the same name are allowed, so 11 | # you can also view it as a map from names to a list of values. 12 | class XiphComment < TagLib::Tag 13 | # Add a name-value pair to the comment. 14 | # 15 | # @param [String] name field name 16 | # @param [String] value field value 17 | # @param [Boolean] replace if true, all existing fields with the 18 | # given name will be replaced 19 | # @return [void] 20 | def add_field(name, value, replace=true) 21 | end 22 | 23 | # Check if the comment contains a field. 24 | # 25 | # @param [String] name field name 26 | # @return [Boolean] 27 | def contains?(name) 28 | end 29 | 30 | # Count the number of fields (including the pictures). 31 | # 32 | # @return [Integer] the number of fields in the comment (name-value pairs) 33 | def field_count 34 | end 35 | 36 | # Get the contents of the comment as a hash, with the key being a 37 | # field name String and the value a list of field values for that 38 | # key. Example result: 39 | # 40 | # { 'TITLE' => ["Title"], 41 | # 'GENRE' => ["Rock", "Pop"] } 42 | # 43 | # Note that the returned hash is read-only. Changing it will have no 44 | # effect on the comment; use {#add_field} and {#remove_fields} for 45 | # that. 46 | # 47 | # @return [Hash>] a hash from field names to 48 | # value lists 49 | def field_list_map 50 | end 51 | 52 | # Remove one or more fields. 53 | # 54 | # @overload remove_field(name) 55 | # Removes all fields with the given name. 56 | # 57 | # @param [String] name field name 58 | # 59 | # @overload remove_field(name, value) 60 | # Removes the field with the given name and value. 61 | # 62 | # @param [String] name field name 63 | # @param [String] value field value 64 | # 65 | # @return [void] 66 | # 67 | # @since 1.0.0 68 | def remove_fields 69 | end 70 | 71 | # Remove all the fields. 72 | # 73 | # @since 1.0.0 74 | def remove_all_fields 75 | end 76 | 77 | # @return [Boolean] True if the specified string is a valid Xiph comment key. 78 | # 79 | # @since 1.0.0 80 | def self.check_key(key) 81 | end 82 | 83 | # @return [Array] The list of the pictures associated to this comment. 84 | # 85 | # @since 1.0.0 86 | def picture_list 87 | end 88 | 89 | # Add a picture. 90 | # @param [TagLib::FLAC::Picture] picture 91 | # @return [void] 92 | # 93 | # @since 1.0.0 94 | def add_picture(picture) 95 | end 96 | 97 | # Remove a picture. 98 | # @param [TagLib::FLAC::Picture] picture 99 | # @return [void] 100 | # 101 | # @since 1.0.0 102 | def remove_picture(picture) 103 | end 104 | 105 | # Remove all the pictures. 106 | # @return [void] 107 | # 108 | # @since 1.0.0 109 | def remove_all_pictures 110 | end 111 | 112 | # @return [String] vendor ID of the encoder used 113 | attr_reader :vendor_id 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /docs/taglib/riff.rb: -------------------------------------------------------------------------------- 1 | # @since 0.7.0 2 | module TagLib::RIFF 3 | end 4 | -------------------------------------------------------------------------------- /docs/taglib/vorbis.rb: -------------------------------------------------------------------------------- 1 | module TagLib::Ogg::Vorbis 2 | # The file class for `.ogg` and other `.oga` files. 3 | # 4 | # @example Reading Vorbis comments 5 | # TagLib::Ogg::Vorbis::File.open("file.oga") do |file| 6 | # tag = file.tag 7 | # puts tag.title 8 | # fields = tag.field_list_map 9 | # puts fields['DATE'] 10 | # end 11 | # 12 | class File < TagLib::Ogg::File 13 | # {include:::TagLib::FileRef.open} 14 | # 15 | # @param (see #initialize) 16 | # @yield [file] the {File} object, as obtained by {#initialize} 17 | # @return the return value of the block 18 | # 19 | # @since 0.4.0 20 | def self.open(filename, read_properties=true) 21 | end 22 | 23 | # Load an Ogg Vorbis file. 24 | # 25 | # @param [String] filename 26 | # @param [Boolean] read_properties if audio properties should be 27 | # read 28 | def initialize(filename, read_properties=true) 29 | end 30 | 31 | # Returns the VorbisComment tag. 32 | # 33 | # @return [TagLib::Ogg::XiphComment] 34 | def tag 35 | end 36 | 37 | # Returns audio properties. 38 | # 39 | # @return [TagLib::Ogg::Vorbis::Properties] 40 | def audio_properties 41 | end 42 | end 43 | 44 | # Ogg Vorbis audio properties. 45 | class Properties < TagLib::AudioProperties 46 | # @return [Integer] Vorbis version 47 | attr_reader :vorbis_version 48 | 49 | # @return [Integer] maximum bitrate from Vorbis identification 50 | # header 51 | attr_reader :bitrate_maximum 52 | 53 | # @return [Integer] nominal bitrate from Vorbis identification 54 | # header 55 | attr_reader :bitrate_nominal 56 | 57 | # @return [Integer] minimum bitrate from Vorbis identification 58 | # header 59 | attr_reader :bitrate_minimum 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /docs/taglib/wav.rb: -------------------------------------------------------------------------------- 1 | # @since 0.7.0 2 | module TagLib::RIFF::WAV 3 | FORMAT_UNKNOWN = 0x0000 # @since 1.0.0 4 | FORMAT_PCM = 0x0001 # @since 1.0.0 5 | 6 | # The file class for `.wav` files. 7 | # 8 | # @example Reading the title 9 | # title = TagLib::RIFF::WAV::File.open("sample.wav") do |file| 10 | # file.tag.title 11 | # end 12 | # 13 | # @example Reading WAV-specific audio properties 14 | # TagLib::RIFF::WAV::File.open("sample.wav") do |file| 15 | # file.audio_properties.sample_width #=> 16 16 | # end 17 | # 18 | # @example Saving ID3v2 cover-art to disk 19 | # TagLib::RIFF::WAV::File.open("sample.wav") do |file| 20 | # id3v2_tag = file.tag 21 | # cover = id3v2_tag.frame_list('APIC').first 22 | # ext = cover.mime_type.rpartition('/')[2] 23 | # File.open("cover-art.#{ext}", "wb") { |f| f.write cover.picture } 24 | # end 25 | # 26 | # @see ID3v2::Tag ID3v2 examples. 27 | # 28 | class File < TagLib::File 29 | NoTags = 0x0000 30 | ID3v1 = 0x0001 31 | ID3v2 = 0x0002 32 | APE = 0x0004 33 | AllTags = 0xffff 34 | 35 | # {include:::TagLib::FileRef.open} 36 | # 37 | # @param (see #initialize) 38 | # @yield [file] the {File} object, as obtained by {#initialize} 39 | # @return the return value of the block 40 | # 41 | def self.open(filename, read_properties=true) 42 | end 43 | 44 | # Load a WAV file. 45 | # 46 | # @param [String] filename 47 | # @param [Boolean] read_properties if audio properties should be 48 | # read 49 | def initialize(filename, read_properties=true) 50 | end 51 | 52 | # Returns the ID3v2 tag. 53 | # 54 | # @return [TagLib::ID3v2::Tag] 55 | def tag 56 | end 57 | 58 | # Returns the ID3v2 tag. 59 | # 60 | # @return [TagLib::ID3v2::Tag] 61 | # 62 | # @since 1.0.0 63 | def id3v2_tag 64 | end 65 | 66 | # Returns audio properties. 67 | # 68 | # @return [TagLib::RIFF::WAV::Properties] 69 | def audio_properties 70 | end 71 | 72 | # Remove the tags matching the specified OR-ed types. 73 | # 74 | # @param [int] tags The types of tags to remove. 75 | # @return [void] 76 | # 77 | # @since 1.0.0 78 | def strip(tags=TagLib::RIFF::WAV::File::AllTags) 79 | end 80 | end 81 | 82 | class Properties < TagLib::AudioProperties 83 | # @return [Integer] Number of bits per audio sample. 84 | # 85 | # @since 1.0.0 86 | attr_reader :bits_per_sample 87 | 88 | # @return [Integer] Number of sample frames. 89 | # 90 | # @since 1.0.0 91 | attr_reader :sample_frames 92 | 93 | # @return [Integer] length of the file in seconds 94 | # 95 | # @since 1.0.0 96 | attr_reader :length_in_seconds 97 | 98 | # @return [Integer] length of the file in milliseconds 99 | # 100 | # @since 1.0.0 101 | attr_reader :length_in_milliseconds 102 | 103 | # @return [Integer] The format ID of the file. 104 | # 105 | # 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and 106 | # so forth. For further information, refer to the WAVE Form 107 | # Registration Numbers in RFC 2361. 108 | # 109 | # @since 1.0.0 110 | attr_reader :format 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /ext/extconf_common.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | taglib_dir = ENV['TAGLIB_DIR'] 4 | 5 | unless taglib_dir 6 | # Default opt dirs to help mkmf find taglib 7 | opt_dirs = ['/usr/local', '/opt/local', '/sw'] 8 | 9 | # Heroku vendor dir 10 | vendor = ENV.fetch('GEM_HOME', '')[/^[^ ]*\/vendor\//] 11 | opt_dirs << "#{vendor}taglib" if vendor 12 | opt_dirs_joined = opt_dirs.join(':') 13 | 14 | configure_args = "--with-opt-dir=#{opt_dirs_joined} " 15 | ENV['CONFIGURE_ARGS'] = configure_args + ENV.fetch('CONFIGURE_ARGS', '') 16 | end 17 | 18 | require 'mkmf' 19 | 20 | def error(msg) 21 | message "#{msg}\n" 22 | abort 23 | end 24 | 25 | if taglib_dir && !File.directory?(taglib_dir) 26 | error 'When defined, the TAGLIB_DIR environment variable must point to a valid directory.' 27 | end 28 | 29 | # If specified, use the TAGLIB_DIR environment variable as the prefix 30 | # for finding taglib headers and libs. See MakeMakefile#dir_config 31 | # for more details. 32 | dir_config('tag', taglib_dir) 33 | 34 | # When compiling statically, -lstdc++ would make the resulting .so to 35 | # have a dependency on an external libstdc++ instead of the static one. 36 | unless $LDFLAGS.split(' ').include?('-static-libstdc++') 37 | error 'You must have libstdc++ installed.' unless have_library('stdc++') 38 | end 39 | 40 | unless have_library('tag') 41 | error <<~DESC 42 | You must have taglib installed in order to use taglib-ruby. 43 | 44 | Debian/Ubuntu: sudo apt-get install libtag1-dev 45 | Fedora/RHEL: sudo dnf install taglib-devel 46 | Brew: brew install taglib 47 | MacPorts: sudo port install taglib 48 | DESC 49 | end 50 | 51 | $CFLAGS << ' -DSWIG_TYPE_TABLE=taglib' 52 | 53 | # TagLib 2.0 requires C++17. Some compilers default to an older standard 54 | # so we add this '-std=' option to make sure the compiler accepts C++17 55 | # code. 56 | $CXXFLAGS << ' -std=c++17' 57 | 58 | # Allow users to override the Ruby runtime's preferred CXX 59 | RbConfig::MAKEFILE_CONFIG['CXX'] = ENV['TAGLIB_RUBY_CXX'] if ENV['TAGLIB_RUBY_CXX'] 60 | -------------------------------------------------------------------------------- /ext/taglib_aiff/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_aiff') 7 | -------------------------------------------------------------------------------- /ext/taglib_aiff/taglib_aiff.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::RIFF::AIFF" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | %} 8 | 9 | %include "../taglib_base/includes.i" 10 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 11 | 12 | // Deprecated 13 | %ignore TagLib::RIFF::AIFF::Properties::Properties(const ByteVector&, ReadStyle); 14 | %ignore TagLib::RIFF::AIFF::Properties::length; 15 | %ignore TagLib::RIFF::AIFF::Properties::sampleWidth; 16 | 17 | %ignore TagLib::RIFF::File; 18 | %include 19 | 20 | %include 21 | 22 | %freefunc TagLib::RIFF::AIFF::File "free_taglib_riff_aiff_file"; 23 | 24 | // Ignore IOStream and all the constructors using it. 25 | %ignore IOStream; 26 | %ignore TagLib::RIFF::AIFF::File::File(IOStream *, bool, Properties::ReadStyle, ID3v2::FrameFactory *); 27 | %ignore TagLib::RIFF::AIFF::File::File(IOStream *, bool, Properties::ReadStyle); 28 | %ignore TagLib::RIFF::AIFF::File::File(IOStream *, bool); 29 | %ignore TagLib::RIFF::AIFF::File::File(IOStream *); 30 | %ignore TagLib::RIFF::AIFF::File::isSupported(IOStream *); 31 | 32 | // Ignore the unified property interface. 33 | %ignore TagLib::RIFF::AIFF::File::properties; 34 | %ignore TagLib::RIFF::AIFF::File::setProperties; 35 | %ignore TagLib::RIFF::AIFF::File::removeUnsupportedProperties; 36 | 37 | %rename("id3v2_tag?") TagLib::RIFF::AIFF::File::hasID3v2Tag; 38 | 39 | namespace TagLib { 40 | namespace ID3v2 { 41 | class Tag; 42 | } 43 | } 44 | 45 | %include 46 | 47 | // Unlink Ruby objects from the deleted C++ objects. Otherwise Ruby code 48 | // that calls a method on a tag after the file is deleted segfaults. 49 | %begin %{ 50 | static void free_taglib_riff_aiff_file(void *ptr); 51 | %} 52 | %header %{ 53 | static void free_taglib_riff_aiff_file(void *ptr) { 54 | TagLib::RIFF::AIFF::File *file = (TagLib::RIFF::AIFF::File *) ptr; 55 | 56 | TagLib::ID3v2::Tag *id3v2tag = file->tag(); 57 | if (id3v2tag) { 58 | TagLib::ID3v2::FrameList frames = id3v2tag->frameList(); 59 | for (TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end(); it++) { 60 | TagLib::ID3v2::Frame *frame = (*it); 61 | SWIG_RubyUnlinkObjects(frame); 62 | SWIG_RubyRemoveTracking(frame); 63 | } 64 | 65 | SWIG_RubyUnlinkObjects(id3v2tag); 66 | SWIG_RubyRemoveTracking(id3v2tag); 67 | } 68 | 69 | TagLib::RIFF::AIFF::Properties *properties = file->audioProperties(); 70 | if (properties) { 71 | SWIG_RubyUnlinkObjects(properties); 72 | SWIG_RubyRemoveTracking(properties); 73 | } 74 | 75 | SWIG_RubyUnlinkObjects(ptr); 76 | SWIG_RubyRemoveTracking(ptr); 77 | 78 | delete file; 79 | } 80 | %} 81 | 82 | %extend TagLib::RIFF::AIFF::File { 83 | void close() { 84 | free_taglib_riff_aiff_file($self); 85 | } 86 | } 87 | 88 | 89 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 90 | -------------------------------------------------------------------------------- /ext/taglib_base/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_base') 7 | -------------------------------------------------------------------------------- /ext/taglib_base/includes.i: -------------------------------------------------------------------------------- 1 | %trackobjects; 2 | 3 | // Undefine macros we don't need for wrapping 4 | #define TAGLIB_EXPORT 5 | #define TAGLIB_IGNORE_MISSING_DESTRUCTOR 6 | #define TAGLIB_DEPRECATED 7 | #define TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE 8 | 9 | // Replaces the typemap from swigtype.swg and just adds the line 10 | // SWIG_RubyUnlinkObjects. This is done to be safe in the case when a 11 | // disowned object is deleted by C++ (e.g. with remove_frame). 12 | %typemap(in, noblock=1) SWIGTYPE *DISOWN (int res = 0) { 13 | res = SWIG_ConvertPtr($input, %as_voidptrptr(&$1), $descriptor, SWIG_POINTER_DISOWN | %convertptr_flags); 14 | if (!SWIG_IsOK(res)) { 15 | %argument_fail(res,"$type", $symname, $argnum); 16 | } 17 | SWIG_RubyUnlinkObjects($1); 18 | SWIG_RubyRemoveTracking($1); 19 | } 20 | 21 | %{ 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #if defined(HAVE_RUBY_ENCODING_H) && HAVE_RUBY_ENCODING_H 29 | # include 30 | # define ASSOCIATE_UTF8_ENCODING(value) rb_enc_associate(value, rb_utf8_encoding()); 31 | # define ASSOCIATE_FILESYSTEM_ENCODING(value) rb_enc_associate(value, rb_filesystem_encoding()); 32 | # define CONVERT_TO_UTF8(value) rb_str_export_to_enc(value, rb_utf8_encoding()) 33 | #else 34 | # define ASSOCIATE_UTF8_ENCODING(value) /* nothing */ 35 | # define ASSOCIATE_FILESYSTEM_ENCODING(value) 36 | # define CONVERT_TO_UTF8(value) value 37 | #endif 38 | 39 | VALUE taglib_bytevector_to_ruby_string(const TagLib::ByteVector &byteVector) { 40 | return rb_str_new(byteVector.data(), byteVector.size()); 41 | } 42 | 43 | TagLib::ByteVector ruby_string_to_taglib_bytevector(VALUE s) { 44 | if (NIL_P(s)) { 45 | return TagLib::ByteVector(); 46 | } else { 47 | return TagLib::ByteVector(RSTRING_PTR(StringValue(s)), RSTRING_LEN(s)); 48 | } 49 | } 50 | 51 | VALUE taglib_string_to_ruby_string(const TagLib::String & string) { 52 | VALUE result = rb_str_new2(string.toCString(true)); 53 | ASSOCIATE_UTF8_ENCODING(result); 54 | return result; 55 | } 56 | 57 | TagLib::String ruby_string_to_taglib_string(VALUE s) { 58 | if (NIL_P(s)) { 59 | return TagLib::String(); 60 | } else { 61 | return TagLib::String(RSTRING_PTR(CONVERT_TO_UTF8(StringValue(s))), TagLib::String::UTF8); 62 | } 63 | } 64 | 65 | VALUE taglib_string_list_to_ruby_array(const TagLib::StringList & list) { 66 | VALUE ary = rb_ary_new2(list.size()); 67 | for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); it++) { 68 | VALUE s = taglib_string_to_ruby_string(*it); 69 | rb_ary_push(ary, s); 70 | } 71 | return ary; 72 | } 73 | 74 | TagLib::StringList ruby_array_to_taglib_string_list(VALUE ary) { 75 | TagLib::StringList result = TagLib::StringList(); 76 | if (NIL_P(ary)) { 77 | return result; 78 | } 79 | for (long i = 0; i < RARRAY_LEN(ary); i++) { 80 | VALUE e = rb_ary_entry(ary, i); 81 | TagLib::String s = ruby_string_to_taglib_string(e); 82 | result.append(s); 83 | } 84 | return result; 85 | } 86 | 87 | VALUE taglib_bytevectorlist_to_ruby_array(const TagLib::ByteVectorList & list) { 88 | VALUE ary = rb_ary_new2(list.size()); 89 | for (TagLib::ByteVectorList::ConstIterator it = list.begin(); it != list.end(); it++) { 90 | VALUE s = taglib_bytevector_to_ruby_string(*it); 91 | rb_ary_push(ary, s); 92 | } 93 | return ary; 94 | } 95 | 96 | TagLib::ByteVectorList ruby_array_to_taglib_bytevectorlist(VALUE ary) { 97 | TagLib::ByteVectorList result = TagLib::ByteVectorList(); 98 | if (NIL_P(ary)) { 99 | return result; 100 | } 101 | for (long i = 0; i < RARRAY_LEN(ary); i++) { 102 | VALUE e = rb_ary_entry(ary, i); 103 | TagLib::ByteVector s = ruby_string_to_taglib_bytevector(e); 104 | result.append(s); 105 | } 106 | return result; 107 | } 108 | 109 | VALUE taglib_filename_to_ruby_string(TagLib::FileName filename) { 110 | VALUE result; 111 | #ifdef _WIN32 112 | const char *s = (const char *) filename; 113 | result = rb_str_new2(s); 114 | #else 115 | result = rb_str_new2(filename); 116 | #endif 117 | ASSOCIATE_FILESYSTEM_ENCODING(result); 118 | return result; 119 | } 120 | 121 | TagLib::FileName ruby_string_to_taglib_filename(VALUE s) { 122 | #ifdef _WIN32 123 | #if defined(HAVE_RUBY_ENCODING_H) && HAVE_RUBY_ENCODING_H 124 | VALUE ospath; 125 | const char *utf8; 126 | int len; 127 | wchar_t *wide; 128 | 129 | ospath = rb_str_encode_ospath(s); 130 | utf8 = StringValuePtr(ospath); 131 | len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); 132 | if (!(wide = (wchar_t *) xmalloc(sizeof(wchar_t) * len))) { 133 | return TagLib::FileName((const char *) NULL); 134 | } 135 | MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len); 136 | TagLib::FileName filename(wide); 137 | xfree(wide); 138 | return filename; 139 | #else 140 | const char *filename = StringValuePtr(s); 141 | return TagLib::FileName(filename); 142 | #endif 143 | #else 144 | return StringValuePtr(s); 145 | #endif 146 | } 147 | 148 | VALUE taglib_offset_t_to_ruby_int(TagLib::offset_t off) { 149 | #ifdef _WIN32 150 | return LL2NUM(off); 151 | #else 152 | return OFFT2NUM(off); 153 | #endif 154 | } 155 | %} 156 | 157 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 158 | -------------------------------------------------------------------------------- /ext/taglib_base/taglib_base.i: -------------------------------------------------------------------------------- 1 | %module "TagLib" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | %} 10 | 11 | %include "includes.i" 12 | 13 | namespace TagLib { 14 | class StringList; 15 | class ByteVector; 16 | class ByteVectorList; 17 | 18 | class String { 19 | public: 20 | enum Type { Latin1 = 0, UTF16 = 1, UTF16BE = 2, UTF8 = 3, UTF16LE = 4 }; 21 | }; 22 | 23 | class FileName; 24 | 25 | typedef wchar_t wchar; 26 | typedef unsigned char uchar; 27 | typedef unsigned int uint; 28 | typedef unsigned long ulong; 29 | using offset_t = long long; 30 | } 31 | 32 | %constant int TAGLIB_MAJOR_VERSION = TAGLIB_MAJOR_VERSION; 33 | %constant int TAGLIB_MINOR_VERSION = TAGLIB_MINOR_VERSION; 34 | %constant int TAGLIB_PATCH_VERSION = TAGLIB_PATCH_VERSION; 35 | 36 | // Rename setters to Ruby convention (combining SWIG rename functions 37 | // does not seem to be possible, thus resort to some magic) 38 | // We used to do this with one "command" filter, but support for that was 39 | // removed in SWIG 4.1.0. This is a bit uglier because we need one filter for 40 | // setOne, one for setOneTwo, and one for setOneTwoThree. Looks like we don't 41 | // need one for 4 yet. 42 | // setFoo -> foo= 43 | %rename("%(regex:/set([A-Z][a-z]*)([A-Z][a-z]*)([A-Z][a-z]*)/\\L\\1_\\2_\\3=/)s", 44 | regexmatch$name="^set([A-Z][a-z]*){3}$") ""; 45 | %rename("%(regex:/set([A-Z][a-z]*)([A-Z][a-z]*)/\\L\\1_\\2=/)s", 46 | regexmatch$name="^set([A-Z][a-z]*){2}$") ""; 47 | %rename("%(regex:/set([A-Z][a-z]*)/\\L\\1=/)s", 48 | regexmatch$name="^set([A-Z][a-z]*)$") ""; 49 | 50 | // isFoo -> foo? 51 | %rename("%(regex:/is([A-Z][a-z]*)([A-Z][a-z]*)([A-Z][a-z]*)/\\L\\1_\\2_\\3?/)s", 52 | regexmatch$name="^is([A-Z][a-z]*){3}$") ""; 53 | %rename("%(regex:/is([A-Z][a-z]*)([A-Z][a-z]*)/\\L\\1_\\2?/)s", 54 | regexmatch$name="^is([A-Z][a-z]*){2}$") ""; 55 | %rename("%(regex:/is([A-Z][a-z]*)/\\L\\1?/)s", 56 | regexmatch$name="^is([A-Z][a-z]*)$") ""; 57 | 58 | // ByteVector 59 | %typemap(out) TagLib::ByteVector { 60 | $result = taglib_bytevector_to_ruby_string($1); 61 | } 62 | %typemap(out) TagLib::ByteVector * { 63 | $result = taglib_bytevector_to_ruby_string(*($1)); 64 | } 65 | %typemap(in) TagLib::ByteVector & (TagLib::ByteVector tmp) { 66 | tmp = ruby_string_to_taglib_bytevector($input); 67 | $1 = &tmp; 68 | } 69 | %typemap(in) TagLib::ByteVector * (TagLib::ByteVector tmp) { 70 | tmp = ruby_string_to_taglib_bytevector($input); 71 | $1 = &tmp; 72 | } 73 | %typemap(typecheck) const TagLib::ByteVector & = char *; 74 | 75 | // ByteVectorList 76 | %typemap(out) TagLib::ByteVectorList { 77 | $result = taglib_bytevectorlist_to_ruby_array($1); 78 | } 79 | %typemap(out) TagLib::ByteVectorList * { 80 | $result = taglib_bytevectorlist_to_ruby_array(*($1)); 81 | } 82 | %typemap(in) TagLib::ByteVectorList & (TagLib::ByteVectorList tmp) { 83 | tmp = ruby_array_to_taglib_bytevectorlist($input); 84 | $1 = &tmp; 85 | } 86 | %typemap(in) TagLib::ByteVectorList * (TagLib::ByteVectorList tmp) { 87 | tmp = ruby_array_to_taglib_bytevectorlist($input); 88 | $1 = &tmp; 89 | } 90 | 91 | // String 92 | %typemap(out) TagLib::String { 93 | $result = taglib_string_to_ruby_string($1); 94 | } 95 | // tmp is used for having a local variable that is destroyed at the end 96 | // of the function. Doing "new TagLib::String" would be a big no-no. 97 | %typemap(in) TagLib::String (TagLib::String tmp) { 98 | tmp = ruby_string_to_taglib_string($input); 99 | $1 = &tmp; 100 | } 101 | %typemap(typecheck) TagLib::String = char *; 102 | %apply TagLib::String { TagLib::String &, const TagLib::String & }; 103 | 104 | // StringList 105 | %typemap(out) TagLib::StringList { 106 | $result = taglib_string_list_to_ruby_array($1); 107 | } 108 | %typemap(in) TagLib::StringList (TagLib::StringList tmp) { 109 | tmp = ruby_array_to_taglib_string_list($input); 110 | $1 = &tmp; 111 | } 112 | %typemap(typecheck, precedence=SWIG_TYPECHECK_LIST) TagLib::StringList { 113 | $1 = TYPE($input) == T_ARRAY ? 1 : 0; 114 | } 115 | %apply TagLib::StringList { TagLib::StringList &, const TagLib::StringList & }; 116 | 117 | %typemap(out) TagLib::FileName { 118 | $result = taglib_filename_to_ruby_string($1); 119 | } 120 | %typemap(in) TagLib::FileName { 121 | $1 = ruby_string_to_taglib_filename($input); 122 | if ((const char *)(TagLib::FileName)($1) == NULL) { 123 | SWIG_exception_fail(SWIG_MemoryError, "Failed to allocate memory for file name."); 124 | } 125 | } 126 | %typemap(typecheck) TagLib::FileName = char *; 127 | %feature("valuewrapper") TagLib::FileName; 128 | 129 | %typemap(out) TagLib::offset_t { 130 | $result = taglib_offset_t_to_ruby_int($1); 131 | } 132 | 133 | %ignore TagLib::List::operator[]; 134 | %ignore TagLib::List::operator=; 135 | %ignore TagLib::List::operator!=; 136 | %include 137 | 138 | // Ignore the unified property interface. 139 | %ignore TagLib::Tag::properties; 140 | %ignore TagLib::Tag::setProperties; 141 | %ignore TagLib::Tag::removeUnsupportedProperties; 142 | 143 | %ignore TagLib::Tag::complexProperties; 144 | %ignore TagLib::Tag::setComplexProperties; 145 | %ignore TagLib::Tag::complexPropertyKeys; 146 | 147 | %include 148 | 149 | %ignore TagLib::AudioProperties::length; // Deprecated. 150 | %include 151 | 152 | %ignore TagLib::FileName; 153 | 154 | // Ignore the unified property interface. 155 | %ignore TagLib::File::properties; 156 | %ignore TagLib::File::setProperties; 157 | %ignore TagLib::File::removeUnsupportedProperties; 158 | 159 | %ignore TagLib::File::complexProperties; 160 | %ignore TagLib::File::setComplexProperties; 161 | %ignore TagLib::File::complexPropertyKeys; 162 | 163 | %include 164 | 165 | %ignore TagLib::FileRef::properties; 166 | %ignore TagLib::FileRef::setProperties; 167 | %ignore TagLib::FileRef::removeUnsupportedProperties; 168 | 169 | %ignore TagLib::FileRef::complexProperties; 170 | %ignore TagLib::FileRef::setComplexProperties; 171 | %ignore TagLib::FileRef::complexPropertyKeys; 172 | 173 | // Ignore IOStream and all the constructors using it. 174 | %ignore IOStream; 175 | %ignore TagLib::FileRef::FileRef(IOStream*, bool, AudioProperties::ReadStyle); 176 | %ignore TagLib::FileRef::FileRef(IOStream*, bool); 177 | %ignore TagLib::FileRef::FileRef(IOStream*); 178 | 179 | %ignore TagLib::FileRef::swap; // Only useful internally. 180 | 181 | %ignore TagLib::FileRef::operator=; 182 | %ignore TagLib::FileRef::operator!=; 183 | %warnfilter(SWIGWARN_PARSE_NAMED_NESTED_CLASS) TagLib::FileRef::FileTypeResolver; 184 | %include 185 | 186 | %begin %{ 187 | static void free_taglib_fileref(void *ptr); 188 | %} 189 | %header %{ 190 | static void free_taglib_fileref(void *ptr) { 191 | TagLib::FileRef *fileref = (TagLib::FileRef *) ptr; 192 | 193 | TagLib::Tag *tag = fileref->tag(); 194 | if (tag) { 195 | SWIG_RubyUnlinkObjects(tag); 196 | SWIG_RubyRemoveTracking(tag); 197 | } 198 | 199 | TagLib::AudioProperties *properties = fileref->audioProperties(); 200 | if (properties) { 201 | SWIG_RubyUnlinkObjects(properties); 202 | SWIG_RubyRemoveTracking(properties); 203 | } 204 | 205 | SWIG_RubyUnlinkObjects(ptr); 206 | SWIG_RubyRemoveTracking(ptr); 207 | 208 | delete fileref; 209 | } 210 | %} 211 | 212 | %extend TagLib::FileRef { 213 | void close() { 214 | free_taglib_fileref($self); 215 | } 216 | } 217 | 218 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 219 | -------------------------------------------------------------------------------- /ext/taglib_flac/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_flac') 7 | -------------------------------------------------------------------------------- /ext/taglib_flac/taglib_flac.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::FLAC" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | %} 9 | 10 | %include "../taglib_base/includes.i" 11 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 12 | %include "../taglib_flac_picture/includes.i" 13 | %import(module="taglib_flac_picture") "../taglib_flac_picture/taglib_flac_picture.i" 14 | 15 | // Deprecated 16 | %ignore TagLib::FLAC::Properties::length; 17 | %ignore TagLib::FLAC::Properties::sampleWidth; 18 | 19 | %include 20 | 21 | // Ignore IOStream and all the constructors using it. 22 | %ignore IOStream; 23 | %ignore TagLib::FLAC::File::File(IOStream*, bool, Properties::ReadStyle, ID3v2::FrameFactory *); 24 | %ignore TagLib::FLAC::File::File(IOStream*, bool, Properties::ReadStyle); 25 | %ignore TagLib::FLAC::File::File(IOStream*, bool); 26 | %ignore TagLib::FLAC::File::File(IOStream*); 27 | %ignore TagLib::FLAC::File::File(IOStream*, ID3v2::FrameFactory *, bool, Properties::ReadStyle); 28 | %ignore TagLib::FLAC::File::File(IOStream*, ID3v2::FrameFactory *, bool); 29 | %ignore TagLib::FLAC::File::File(IOStream*, ID3v2::FrameFactory *); 30 | %ignore TagLib::FLAC::File::isSupported(IOStream *); 31 | 32 | %rename(id3v1_tag) TagLib::FLAC::File::ID3v1Tag; 33 | %rename(id3v2_tag) TagLib::FLAC::File::ID3v2Tag; 34 | %rename(set_id3v2_frame_factory) TagLib::FLAC::File::setID3v2FrameFactory; 35 | 36 | %freefunc TagLib::FLAC::File "free_taglib_flac_file"; 37 | 38 | %apply SWIGTYPE *DISOWN { TagLib::FLAC::Picture *picture }; 39 | // Don't expose second parameter, memory should be freed by TagLib 40 | %ignore TagLib::FLAC::File::removePicture(Picture *, bool); 41 | 42 | %rename("xiph_comment?") TagLib::FLAC::File::hasXiphComment; 43 | %rename("id3v1_tag?") TagLib::FLAC::File::hasID3v1Tag; 44 | %rename("id3v2_tag?") TagLib::FLAC::File::hasID3v2Tag; 45 | 46 | %include 47 | 48 | // Unlink Ruby objects from the deleted C++ objects. Otherwise Ruby code 49 | // that calls a method on a tag after the file is deleted segfaults. 50 | %begin %{ 51 | static void free_taglib_flac_file(void *ptr); 52 | %} 53 | %header %{ 54 | static void free_taglib_flac_file(void *ptr) { 55 | TagLib::FLAC::File *file = (TagLib::FLAC::File *) ptr; 56 | 57 | TagLib::Tag *tag = file->tag(); 58 | if (tag) { 59 | SWIG_RubyUnlinkObjects(tag); 60 | SWIG_RubyRemoveTracking(tag); 61 | } 62 | 63 | TagLib::ID3v1::Tag *id3v1tag = file->ID3v1Tag(false); 64 | if (id3v1tag) { 65 | SWIG_RubyUnlinkObjects(id3v1tag); 66 | SWIG_RubyRemoveTracking(id3v1tag); 67 | } 68 | 69 | TagLib::ID3v2::Tag *id3v2tag = file->ID3v2Tag(false); 70 | if (id3v2tag) { 71 | TagLib::ID3v2::FrameList frames = id3v2tag->frameList(); 72 | for (TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end(); it++) { 73 | TagLib::ID3v2::Frame *frame = (*it); 74 | SWIG_RubyUnlinkObjects(frame); 75 | SWIG_RubyRemoveTracking(frame); 76 | } 77 | 78 | SWIG_RubyUnlinkObjects(id3v2tag); 79 | SWIG_RubyRemoveTracking(id3v2tag); 80 | } 81 | 82 | TagLib::Ogg::XiphComment *xiphComment = file->xiphComment(false); 83 | if (xiphComment) { 84 | SWIG_RubyUnlinkObjects(xiphComment); 85 | SWIG_RubyRemoveTracking(xiphComment); 86 | } 87 | 88 | TagLib::FLAC::Properties *properties = file->audioProperties(); 89 | if (properties) { 90 | SWIG_RubyUnlinkObjects(properties); 91 | SWIG_RubyRemoveTracking(properties); 92 | } 93 | 94 | TagLib::List list = file->pictureList(); 95 | for (TagLib::List::ConstIterator it = list.begin(); it != list.end(); it++) { 96 | TagLib::FLAC::Picture *picture = (*it); 97 | SWIG_RubyUnlinkObjects(picture); 98 | SWIG_RubyRemoveTracking(picture); 99 | } 100 | 101 | SWIG_RubyUnlinkObjects(ptr); 102 | SWIG_RubyRemoveTracking(ptr); 103 | 104 | delete file; 105 | } 106 | %} 107 | 108 | %extend TagLib::FLAC::File { 109 | void close() { 110 | free_taglib_flac_file($self); 111 | } 112 | } 113 | 114 | 115 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 116 | -------------------------------------------------------------------------------- /ext/taglib_flac_picture/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_flac_picture') 7 | -------------------------------------------------------------------------------- /ext/taglib_flac_picture/includes.i: -------------------------------------------------------------------------------- 1 | %{ 2 | VALUE taglib_flac_picturelist_to_ruby_array(const TagLib::List & list) { 3 | VALUE ary = rb_ary_new2(list.size()); 4 | for (TagLib::List::ConstIterator it = list.begin(); it != list.end(); it++) { 5 | TagLib::FLAC::Picture *picture = *it; 6 | VALUE p = SWIG_NewPointerObj(picture, SWIGTYPE_p_TagLib__FLAC__Picture, 0); 7 | rb_ary_push(ary, p); 8 | } 9 | return ary; 10 | } 11 | %} 12 | 13 | %typemap(out) TagLib::List { 14 | $result = taglib_flac_picturelist_to_ruby_array($1); 15 | } 16 | -------------------------------------------------------------------------------- /ext/taglib_flac_picture/taglib_flac_picture.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::FLAC" 2 | %{ 3 | #include 4 | #include 5 | %} 6 | 7 | %include "../taglib_base/includes.i" 8 | %include "../taglib_flac_picture/includes.i" 9 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 10 | 11 | %ignore TagLib::FLAC::MetadataBlock::render; // Only useful internally. 12 | %include 13 | 14 | %ignore TagLib::Utils::pictureTypeToString; 15 | %ignore TagLib::Utils::pictureTypeFromString; 16 | %include 17 | 18 | %ignore TagLib::FLAC::Picture::render; // Only useful internally. 19 | %include 20 | -------------------------------------------------------------------------------- /ext/taglib_id3v1/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_id3v1') 7 | -------------------------------------------------------------------------------- /ext/taglib_id3v1/taglib_id3v1.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::ID3v1" 2 | %{ 3 | #include 4 | #include 5 | %} 6 | 7 | %include "../taglib_base/includes.i" 8 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 9 | 10 | %include 11 | 12 | %typemap(out) TagLib::ID3v1::GenreMap { 13 | $result = taglib_id3v1_genre_map_to_ruby_hash($1); 14 | } 15 | 16 | %include 17 | 18 | %{ 19 | VALUE taglib_id3v1_genre_map_to_ruby_hash(const TagLib::ID3v1::GenreMap &map) { 20 | VALUE hsh = rb_hash_new(); 21 | for (TagLib::ID3v1::GenreMap::ConstIterator it = map.begin(); it != map.end(); it++) { 22 | rb_hash_aset(hsh, 23 | taglib_string_to_ruby_string(it->first), 24 | INT2NUM(it->second)); 25 | } 26 | return hsh; 27 | } 28 | %} 29 | 30 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 31 | -------------------------------------------------------------------------------- /ext/taglib_id3v2/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_id3v2') 7 | -------------------------------------------------------------------------------- /ext/taglib_id3v2/relativevolumeframe.i: -------------------------------------------------------------------------------- 1 | %rename("bits_representing_peak") TagLib::ID3v2::RelativeVolumeFrame::PeakVolume::bitsRepresentingPeak; 2 | %rename("peak_volume") TagLib::ID3v2::RelativeVolumeFrame::PeakVolume::peakVolume; 3 | 4 | %rename("set_volume_adjustment") TagLib::ID3v2::RelativeVolumeFrame::setVolumeAdjustment; 5 | %rename("set_volume_adjustment_index") TagLib::ID3v2::RelativeVolumeFrame::setVolumeAdjustmentIndex; 6 | %rename("set_peak_volume") TagLib::ID3v2::RelativeVolumeFrame::setPeakVolume; 7 | 8 | %typemap(out) TagLib::List { 9 | VALUE ary = rb_ary_new2($1.size()); 10 | for (TagLib::List::ConstIterator it = $1.begin(); 11 | it != $1.end(); it++) { 12 | VALUE ct = INT2NUM(*it); 13 | rb_ary_push(ary, ct); 14 | } 15 | $result = ary; 16 | } 17 | 18 | %flatnested TagLib::ID3v2::RelativeVolumeFrame::PeakVolume; 19 | 20 | %include 21 | 22 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 23 | -------------------------------------------------------------------------------- /ext/taglib_id3v2/taglib_id3v2.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::ID3v2" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | %} 24 | 25 | %include "../taglib_base/includes.i" 26 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 27 | 28 | namespace TagLib { 29 | namespace ID3v2 { 30 | class FrameFactory; 31 | } 32 | } 33 | 34 | %{ 35 | VALUE taglib_id3v2_frame_to_ruby_object(const TagLib::ID3v2::Frame *frame) { 36 | TagLib::ByteVector id = frame->frameID(); 37 | void *f = SWIG_as_voidptr(frame); 38 | swig_type_info *ti; 39 | if (dynamic_cast(frame)) 40 | ti = SWIGTYPE_p_TagLib__ID3v2__UnknownFrame; 41 | else if (id == "APIC") 42 | ti = SWIGTYPE_p_TagLib__ID3v2__AttachedPictureFrame; 43 | else if (id == "CHAP") 44 | ti = SWIGTYPE_p_TagLib__ID3v2__ChapterFrame; 45 | else if (id == "COMM") 46 | ti = SWIGTYPE_p_TagLib__ID3v2__CommentsFrame; 47 | else if (id == "CTOC") 48 | ti = SWIGTYPE_p_TagLib__ID3v2__TableOfContentsFrame; 49 | else if (id == "GEOB") 50 | ti = SWIGTYPE_p_TagLib__ID3v2__GeneralEncapsulatedObjectFrame; 51 | else if (id == "POPM") 52 | ti = SWIGTYPE_p_TagLib__ID3v2__PopularimeterFrame; 53 | else if (id == "PRIV") 54 | ti = SWIGTYPE_p_TagLib__ID3v2__PrivateFrame; 55 | else if (id == "RVAD" || id == "RVA2") 56 | ti = SWIGTYPE_p_TagLib__ID3v2__RelativeVolumeFrame; 57 | else if (id == "TXXX") 58 | ti = SWIGTYPE_p_TagLib__ID3v2__UserTextIdentificationFrame; 59 | else if (id.startsWith("T")) 60 | ti = SWIGTYPE_p_TagLib__ID3v2__TextIdentificationFrame; 61 | else if (id == "UFID") 62 | ti = SWIGTYPE_p_TagLib__ID3v2__UniqueFileIdentifierFrame; 63 | else if (id == "USLT") 64 | ti = SWIGTYPE_p_TagLib__ID3v2__UnsynchronizedLyricsFrame; 65 | else if (id == "WXXX") 66 | ti = SWIGTYPE_p_TagLib__ID3v2__UserUrlLinkFrame; 67 | else if (id.startsWith("W")) 68 | ti = SWIGTYPE_p_TagLib__ID3v2__UrlLinkFrame; 69 | else 70 | ti = SWIGTYPE_p_TagLib__ID3v2__Frame; 71 | return SWIG_NewPointerObj(f, ti, 0); 72 | } 73 | 74 | VALUE taglib_id3v2_framelist_to_ruby_array(TagLib::ID3v2::FrameList *list) { 75 | VALUE ary = rb_ary_new2(list->size()); 76 | for (TagLib::ID3v2::FrameList::ConstIterator it = list->begin(); it != list->end(); it++) { 77 | VALUE s = taglib_id3v2_frame_to_ruby_object(*it); 78 | rb_ary_push(ary, s); 79 | } 80 | return ary; 81 | } 82 | %} 83 | 84 | %include 85 | %include 86 | 87 | // Only useful internally. 88 | %ignore TagLib::ID3v2::Frame::instrumentPrefix; 89 | %ignore TagLib::ID3v2::Frame::commentPrefix; 90 | %ignore TagLib::ID3v2::Frame::lyricsPrefix; 91 | %ignore TagLib::ID3v2::Frame::urlPrefix; 92 | 93 | %ignore TagLib::ID3v2::Frame::Header; 94 | %ignore TagLib::ID3v2::Frame::headerSize; // Deprecated 95 | %ignore TagLib::ID3v2::Frame::createTextualFrame; 96 | %include 97 | 98 | %typemap(out) TagLib::ID3v2::FrameList & { 99 | $result = taglib_id3v2_framelist_to_ruby_array($1); 100 | } 101 | %apply TagLib::ID3v2::FrameList & { const TagLib::ID3v2::FrameList & }; 102 | 103 | %apply SWIGTYPE *DISOWN { TagLib::ID3v2::Frame *frame }; 104 | %ignore TagLib::ID3v2::Tag::removeFrame(Frame *, bool); // Dont expose second parameter. 105 | %ignore TagLib::ID3v2::Tag::render; // Only useful internally. 106 | %ignore TagLib::ID3v2::Latin1StringHandler; 107 | %ignore TagLib::ID3v2::Tag::latin1StringHandler; 108 | %ignore TagLib::ID3v2::Tag::setLatin1StringHandler; 109 | %include 110 | %clear TagLib::ID3v2::Frame *; 111 | 112 | // Deprecated 113 | %ignore TagLib::ID3v2::FrameFactory::createFrame(const ByteVector &, bool); 114 | %ignore TagLib::ID3v2::FrameFactory::createFrame(const ByteVector &, unsigned int version); 115 | %ignore TagLib::ID3v2::FrameFactory::createFrame(const ByteVector &); 116 | 117 | %ignore TagLib::ID3v2::FrameFactory::rebuildAggregateFrames; // Only useful internally 118 | %include 119 | 120 | // Resolve overloading conflict with setText(String) 121 | %rename("field_list=") TagLib::ID3v2::TextIdentificationFrame::setText(const StringList &); 122 | %rename("from_data") TagLib::ID3v2::TextIdentificationFrame::TextIdentificationFrame(const ByteVector &); 123 | // Appear to be only useful internally. 124 | %ignore TagLib::ID3v2::TextIdentificationFrame::involvedPeopleMap; 125 | 126 | // Ignore the unified property interface. 127 | %ignore TagLib::ID3v2::TextIdentificationFrame::asProperties; 128 | 129 | %ignore TagLib::ID3v2::TextIdentificationFrame::createTIPLFrame; 130 | %ignore TagLib::ID3v2::TextIdentificationFrame::createTMCLFrame; 131 | %ignore TagLib::ID3v2::KeyConversionMap; 132 | 133 | %include "relativevolumeframe.i" 134 | 135 | %ignore TagLib::Utils::pictureTypeToString; 136 | %ignore TagLib::Utils::pictureTypeFromString; 137 | %include 138 | 139 | %include 140 | 141 | // Ignore the unified property interface. 142 | %ignore TagLib::ID3v2::ChapterFrame::asProperties; 143 | %ignore TagLib::ID3v2::CommentsFrame::asProperties; 144 | %ignore TagLib::ID3v2::TableOfContentsFrame::asProperties; 145 | 146 | %rename("element_id=") TagLib::ID3v2::ChapterFrame::setElementID(const ByteVector &eID); 147 | %include 148 | 149 | %include 150 | %include 151 | %include 152 | %include 153 | 154 | %rename("element_id=") TagLib::ID3v2::TableOfContentsFrame::setElementID(const ByteVector &eID); 155 | %include 156 | 157 | %include 158 | 159 | // Ignore the unified property interface. 160 | %ignore TagLib::ID3v2::UniqueFileIdentifierFrame::asProperties; 161 | 162 | %ignore TagLib::ID3v2::UniqueFileIdentifierFrame::findByOwner; 163 | 164 | %include 165 | 166 | %include 167 | 168 | // Ignore the unified property interface. 169 | %ignore TagLib::ID3v2::UnsynchronizedLyricsFrame::asProperties; 170 | 171 | %ignore TagLib::ID3v2::UnsynchronizedLyricsFrame::findByDescription; 172 | 173 | %include 174 | 175 | // Ignore the unified property interface. 176 | %ignore TagLib::ID3v2::UrlLinkFrame::asProperties; 177 | %ignore TagLib::ID3v2::UserUrlLinkFrame::asProperties; 178 | 179 | %ignore TagLib::ID3v2::UserUrlLinkFrame::find; 180 | 181 | %include 182 | 183 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 184 | -------------------------------------------------------------------------------- /ext/taglib_mp4/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_mp4') 7 | -------------------------------------------------------------------------------- /ext/taglib_mpeg/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_mpeg') 7 | -------------------------------------------------------------------------------- /ext/taglib_mpeg/taglib_mpeg.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::MPEG" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | %} 10 | 11 | %include "../taglib_base/includes.i" 12 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 13 | 14 | %ignore TagLib::MPEG::Header::operator=; 15 | %include 16 | 17 | %include 18 | %include 19 | 20 | %ignore TagLib::MPEG::length; // Deprecated. 21 | %include 22 | 23 | %rename(id3v1_tag) TagLib::MPEG::File::ID3v1Tag; 24 | %rename(id3v2_tag) TagLib::MPEG::File::ID3v2Tag; 25 | %rename(set_id3v2_frame_factory) TagLib::MPEG::File::setID3v2FrameFactory; 26 | 27 | %freefunc TagLib::MPEG::File "free_taglib_mpeg_file"; 28 | 29 | // Ignore IOStream and all the constructors using it. 30 | %ignore IOStream; 31 | %ignore TagLib::MPEG::File::File(IOStream *, ID3v2::FrameFactory *, bool, Properties::ReadStyle); 32 | %ignore TagLib::MPEG::File::File(IOStream *, ID3v2::FrameFactory *, bool); 33 | %ignore TagLib::MPEG::File::File(IOStream *, ID3v2::FrameFactory *); 34 | %ignore TagLib::MPEG::File::File(IOStream *, bool, Properties::ReadStyle, ID3v2::FrameFactory *); 35 | %ignore TagLib::MPEG::File::File(IOStream *, bool, Properties::ReadStyle); 36 | %ignore TagLib::MPEG::File::File(IOStream *, bool); 37 | %ignore TagLib::MPEG::File::File(IOStream *); 38 | %ignore TagLib::MPEG::File::isSupported(IOStream *); 39 | %rename("id3v1_tag?") TagLib::MPEG::File::hasID3v1Tag; 40 | %rename("id3v2_tag?") TagLib::MPEG::File::hasID3v2Tag; 41 | %rename("ape_tag?") TagLib::MPEG::File::hasAPETag; 42 | %include 43 | 44 | // Unlink Ruby objects from the deleted C++ objects. Otherwise Ruby code 45 | // that calls a method on a tag after the file is deleted segfaults. 46 | %begin %{ 47 | static void free_taglib_mpeg_file(void *ptr); 48 | %} 49 | %header %{ 50 | static void free_taglib_mpeg_file(void *ptr) { 51 | TagLib::MPEG::File *file = (TagLib::MPEG::File *) ptr; 52 | 53 | TagLib::Tag *tag = file->tag(); 54 | if (tag) { 55 | SWIG_RubyUnlinkObjects(tag); 56 | SWIG_RubyRemoveTracking(tag); 57 | } 58 | 59 | TagLib::ID3v1::Tag *id3v1tag = file->ID3v1Tag(false); 60 | if (id3v1tag) { 61 | SWIG_RubyUnlinkObjects(id3v1tag); 62 | SWIG_RubyRemoveTracking(id3v1tag); 63 | } 64 | 65 | TagLib::ID3v2::Tag *id3v2tag = file->ID3v2Tag(false); 66 | if (id3v2tag) { 67 | TagLib::ID3v2::FrameList frames = id3v2tag->frameList(); 68 | for (TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end(); it++) { 69 | TagLib::ID3v2::Frame *frame = (*it); 70 | SWIG_RubyUnlinkObjects(frame); 71 | SWIG_RubyRemoveTracking(frame); 72 | } 73 | 74 | SWIG_RubyUnlinkObjects(id3v2tag); 75 | SWIG_RubyRemoveTracking(id3v2tag); 76 | } 77 | 78 | TagLib::MPEG::Properties *properties = file->audioProperties(); 79 | if (properties) { 80 | SWIG_RubyUnlinkObjects(properties); 81 | SWIG_RubyRemoveTracking(properties); 82 | } 83 | 84 | SWIG_RubyUnlinkObjects(ptr); 85 | SWIG_RubyRemoveTracking(ptr); 86 | 87 | delete file; 88 | } 89 | %} 90 | 91 | %extend TagLib::MPEG::File { 92 | void close() { 93 | free_taglib_mpeg_file($self); 94 | } 95 | } 96 | 97 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 98 | -------------------------------------------------------------------------------- /ext/taglib_ogg/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_ogg') 7 | -------------------------------------------------------------------------------- /ext/taglib_ogg/taglib_ogg.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::Ogg" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | %} 8 | 9 | %include "../taglib_base/includes.i" 10 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 11 | %include "../taglib_flac_picture/includes.i" 12 | %import(module="taglib_flac_picture") "../taglib_flac_picture/taglib_flac_picture.i" 13 | 14 | %typemap(out) TagLib::Ogg::FieldListMap { 15 | $result = taglib_ogg_fieldlistmap_to_ruby_hash(*$1); 16 | } 17 | %apply TagLib::Ogg::FieldListMap { const TagLib::Ogg::FieldListMap & }; 18 | 19 | %include 20 | 21 | %apply SWIGTYPE *DISOWN { TagLib::FLAC::Picture *picture }; 22 | // Don't expose second parameter, memory should be freed by TagLib 23 | %ignore TagLib::Ogg::XiphComment::removePicture(Picture *, bool); 24 | 25 | %ignore TagLib::Ogg::XiphComment::removeField; 26 | 27 | %rename("contains?") TagLib::Ogg::XiphComment::contains; 28 | 29 | %include 30 | 31 | %{ 32 | VALUE taglib_ogg_fieldlistmap_to_ruby_hash(const TagLib::Ogg::FieldListMap & map) { 33 | VALUE hash = rb_hash_new(); 34 | for (TagLib::Ogg::FieldListMap::ConstIterator it = map.begin(); it != map.end(); it++) { 35 | TagLib::String key = (*it).first; 36 | TagLib::StringList value = (*it).second; 37 | VALUE k = taglib_string_to_ruby_string(key); 38 | VALUE v = taglib_string_list_to_ruby_array(value); 39 | rb_hash_aset(hash, k, v); 40 | } 41 | return hash; 42 | } 43 | %} 44 | 45 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 46 | -------------------------------------------------------------------------------- /ext/taglib_vorbis/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_vorbis') 7 | -------------------------------------------------------------------------------- /ext/taglib_vorbis/taglib_vorbis.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::Ogg::Vorbis" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | %} 7 | 8 | %include "../taglib_base/includes.i" 9 | %import(module="taglib_ogg") "../taglib_ogg/taglib_ogg.i" 10 | 11 | %freefunc TagLib::Vorbis::File "free_taglib_vorbis_file"; 12 | 13 | // Ignore IOStream and all the constructors using it. 14 | %ignore IOStream; 15 | %ignore TagLib::Vorbis::File::File(IOStream *, bool, Properties::ReadStyle); 16 | %ignore TagLib::Vorbis::File::File(IOStream *, bool); 17 | %ignore TagLib::Vorbis::File::File(IOStream *); 18 | %ignore TagLib::Vorbis::File::isSupported(IOStream *); 19 | 20 | %ignore TagLib::Vorbis::Properties::length; // Deprecated. 21 | %include 22 | 23 | %include 24 | 25 | %begin %{ 26 | static void free_taglib_vorbis_file(void *ptr); 27 | %} 28 | %header %{ 29 | static void free_taglib_vorbis_file(void *ptr) { 30 | TagLib::Ogg::Vorbis::File *file = (TagLib::Ogg::Vorbis::File *) ptr; 31 | 32 | TagLib::Ogg::XiphComment *tag = file->tag(); 33 | if (tag) { 34 | SWIG_RubyUnlinkObjects(tag); 35 | SWIG_RubyRemoveTracking(tag); 36 | } 37 | 38 | TagLib::Vorbis::Properties *properties = file->audioProperties(); 39 | if (properties) { 40 | SWIG_RubyUnlinkObjects(properties); 41 | SWIG_RubyRemoveTracking(properties); 42 | } 43 | 44 | SWIG_RubyUnlinkObjects(file); 45 | SWIG_RubyRemoveTracking(file); 46 | 47 | delete file; 48 | } 49 | %} 50 | 51 | %extend TagLib::Vorbis::File { 52 | void close() { 53 | free_taglib_vorbis_file($self); 54 | } 55 | } 56 | 57 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 58 | -------------------------------------------------------------------------------- /ext/taglib_wav/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) 4 | require 'extconf_common' 5 | 6 | create_makefile('taglib_wav') 7 | -------------------------------------------------------------------------------- /ext/taglib_wav/taglib_wav.i: -------------------------------------------------------------------------------- 1 | %module "TagLib::RIFF::WAV" 2 | %{ 3 | #include 4 | #include 5 | #include 6 | #include 7 | %} 8 | 9 | %include "../taglib_base/includes.i" 10 | %import(module="taglib_base") "../taglib_base/taglib_base.i" 11 | 12 | // Deprecated 13 | %ignore TagLib::RIFF::WAV::Properties::Properties(const ByteVector&, ReadStyle); 14 | %ignore TagLib::RIFF::WAV::Properties::Properties(const ByteVector&, unsigned int, ReadStyle); 15 | %ignore TagLib::RIFF::WAV::Properties::length; 16 | %ignore TagLib::RIFF::WAV::Properties::sampleWidth; 17 | 18 | %ignore TagLib::RIFF::File; 19 | %include 20 | 21 | %include 22 | 23 | %freefunc TagLib::RIFF::WAV::File "free_taglib_riff_wav_file"; 24 | 25 | namespace TagLib { 26 | namespace ID3v2 { 27 | class Tag; 28 | } 29 | } 30 | 31 | // Ignore IOStream and all the constructors using it. 32 | %ignore IOStream; 33 | %ignore TagLib::RIFF::WAV::File::File(IOStream *, bool, Properties::ReadStyle, ID3v2::FrameFactory *f); 34 | %ignore TagLib::RIFF::WAV::File::File(IOStream *, bool, Properties::ReadStyle); 35 | %ignore TagLib::RIFF::WAV::File::File(IOStream *, bool); 36 | %ignore TagLib::RIFF::WAV::File::File(IOStream *); 37 | %ignore TagLib::RIFF::WAV::File::isSupported(IOStream *); 38 | 39 | // Ignore the unified property interface. 40 | %ignore TagLib::RIFF::WAV::File::properties; 41 | %ignore TagLib::RIFF::WAV::File::setProperties; 42 | %ignore TagLib::RIFF::WAV::File::removeUnsupportedProperties; 43 | 44 | %ignore TagLib::RIFF::WAV::File::InfoTag; 45 | 46 | %rename(id3v2_tag) TagLib::RIFF::WAV::File::ID3v2Tag; 47 | %rename("id3v2_tag?") TagLib::RIFF::WAV::File::hasID3v2Tag; 48 | %rename("info_tag?") TagLib::RIFF::WAV::File::hasInfoTag; 49 | 50 | %include 51 | 52 | // Unlink Ruby objects from the deleted C++ objects. Otherwise Ruby code 53 | // that calls a method on a tag after the file is deleted segfaults. 54 | %begin %{ 55 | static void free_taglib_riff_wav_file(void *ptr); 56 | %} 57 | %header %{ 58 | static void free_taglib_riff_wav_file(void *ptr) { 59 | TagLib::RIFF::WAV::File *file = (TagLib::RIFF::WAV::File *) ptr; 60 | 61 | TagLib::ID3v2::Tag *id3v2tag = file->ID3v2Tag(); 62 | if (id3v2tag) { 63 | TagLib::ID3v2::FrameList frames = id3v2tag->frameList(); 64 | for (TagLib::ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end(); it++) { 65 | TagLib::ID3v2::Frame *frame = (*it); 66 | SWIG_RubyUnlinkObjects(frame); 67 | SWIG_RubyRemoveTracking(frame); 68 | } 69 | 70 | SWIG_RubyUnlinkObjects(id3v2tag); 71 | SWIG_RubyRemoveTracking(id3v2tag); 72 | } 73 | 74 | TagLib::RIFF::WAV::Properties *properties = file->audioProperties(); 75 | if (properties) { 76 | SWIG_RubyUnlinkObjects(properties); 77 | SWIG_RubyRemoveTracking(properties); 78 | } 79 | 80 | SWIG_RubyUnlinkObjects(ptr); 81 | SWIG_RubyRemoveTracking(ptr); 82 | 83 | delete file; 84 | } 85 | %} 86 | 87 | %extend TagLib::RIFF::WAV::File { 88 | void close() { 89 | free_taglib_riff_wav_file($self); 90 | } 91 | } 92 | 93 | 94 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 95 | -------------------------------------------------------------------------------- /ext/valgrind-suppressions.txt: -------------------------------------------------------------------------------- 1 | { 2 | taglib_base SWIG_InitializeModule 3 | exp-ptrcheck:SorG 4 | fun:Init_taglib_base 5 | fun:dln_load 6 | } 7 | { 8 | taglib_id3v2 SWIG_InitializeModule 9 | exp-ptrcheck:SorG 10 | fun:Init_taglib_id3v2 11 | fun:dln_load 12 | } 13 | { 14 | taglib_mpeg SWIG_InitializeModule 15 | exp-ptrcheck:SorG 16 | fun:Init_taglib_mpeg 17 | fun:dln_load 18 | } 19 | # Ruby 1.9 VM supressions 20 | { 21 | r1 22 | Memcheck:Cond 23 | fun:gc_mark 24 | } 25 | { 26 | r2 27 | Memcheck:Cond 28 | fun:gc_mark_children 29 | } 30 | { 31 | r3 32 | Memcheck:Cond 33 | fun:mark_current_machine_context 34 | } 35 | { 36 | r4 37 | Memcheck:Value8 38 | fun:gc_mark 39 | } 40 | { 41 | r5 42 | Memcheck:Value8 43 | fun:gc_mark_children 44 | } 45 | { 46 | r6 47 | Memcheck:Value8 48 | fun:mark_current_machine_context 49 | } 50 | { 51 | r7 52 | Memcheck:Value8 53 | fun:vm_exec_core 54 | } 55 | { 56 | r8 57 | Memcheck:Value4 58 | fun:find_derivation 59 | } 60 | { 61 | r9 62 | Memcheck:Value8 63 | fun:__gconv_read_conf 64 | } 65 | { 66 | r10 67 | Memcheck:Leak 68 | fun:malloc 69 | fun:vm_xmalloc 70 | fun:ruby_strdup 71 | } 72 | { 73 | r11 74 | Memcheck:Leak 75 | fun:malloc 76 | fun:vm_xmalloc 77 | fun:rb_include_module 78 | } 79 | { 80 | r12 81 | Memcheck:Leak 82 | ... 83 | fun:ruby_init 84 | fun:main 85 | } 86 | { 87 | r20 88 | exp-ptrcheck:SorG 89 | fun:insert_module 90 | } 91 | { 92 | r21 93 | exp-ptrcheck:SorG 94 | fun:find_derivation 95 | } 96 | { 97 | r22 98 | exp-ptrcheck:SorG 99 | fun:vm_exec_core 100 | } 101 | { 102 | r23 103 | exp-ptrcheck:SorG 104 | fun:__gconv_read_conf 105 | } 106 | { 107 | r24 108 | exp-ptrcheck:SorG 109 | fun:mark_current_machine_context 110 | } 111 | # Ruby 1.8 suppressions 112 | { 113 | r18_1 114 | Memcheck:Cond 115 | fun:mark_locations_array 116 | ... 117 | fun:yycompile 118 | } 119 | { 120 | r18_2 121 | Memcheck:Cond 122 | fun:mark_locations_array 123 | ... 124 | fun:rb_eval 125 | } 126 | { 127 | r18_3 128 | Memcheck:Leak 129 | fun:malloc 130 | fun:ruby_xmalloc 131 | fun:scope_dup 132 | } 133 | { 134 | r18_4 135 | Memcheck:Leak 136 | fun:malloc 137 | ... 138 | fun:yycompile 139 | } 140 | { 141 | r18_5 142 | exp-ptrcheck:SorG 143 | fun:mark_locations_array 144 | fun:garbage_collect 145 | ... 146 | fun:yycompile 147 | } 148 | { 149 | r18_6 150 | exp-ptrcheck:Heap 151 | ... 152 | fun:rb_intern 153 | fun:rb_str_intern 154 | fun:rb_to_id 155 | fun:rb_mod_alias_method 156 | } 157 | { 158 | r18_7 159 | exp-ptrcheck:SorG 160 | fun:ruby_yyparse 161 | fun:yycompile 162 | } 163 | { 164 | r18_8 165 | exp-ptrcheck:SorG 166 | fun:ruby_re_compile_pattern 167 | fun:rb_reg_initialize 168 | ... 169 | fun:rb_class_new_instance 170 | } 171 | -------------------------------------------------------------------------------- /ext/win.cmake: -------------------------------------------------------------------------------- 1 | SET(CMAKE_SYSTEM_NAME Windows) 2 | 3 | SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 4 | SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 5 | SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres) 6 | -------------------------------------------------------------------------------- /lib/taglib.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | platform = RUBY_PLATFORM.split('-')[1] 4 | if platform == 'mingw32' 5 | # Enable loading of pre-compiled libtag.dll 6 | ENV['PATH'] += (File::PATH_SEPARATOR + __dir__) 7 | end 8 | 9 | require 'taglib/version' 10 | require 'taglib/base' 11 | require 'taglib/mpeg' 12 | require 'taglib/id3v1' 13 | require 'taglib/id3v2' 14 | require 'taglib/ogg' 15 | require 'taglib/vorbis' 16 | require 'taglib/flac' 17 | require 'taglib/mp4' 18 | require 'taglib/aiff' 19 | require 'taglib/wav' 20 | -------------------------------------------------------------------------------- /lib/taglib/aiff.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_aiff' 4 | 5 | module TagLib::RIFF::AIFF 6 | class File 7 | extend ::TagLib::FileOpenable 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/taglib/base.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_base' 4 | 5 | module TagLib 6 | module FileOpenable 7 | def open(*args) 8 | file = new(*args) 9 | begin 10 | result = yield file 11 | ensure 12 | file.close 13 | end 14 | result 15 | end 16 | end 17 | 18 | class FileRef 19 | extend FileOpenable 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/taglib/flac.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_flac' 4 | 5 | module TagLib::FLAC 6 | class File 7 | extend ::TagLib::FileOpenable 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/taglib/id3v1.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_id3v1' 4 | -------------------------------------------------------------------------------- /lib/taglib/id3v2.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_id3v2' 4 | 5 | module TagLib 6 | module ID3v2 7 | class Frame 8 | def to_s 9 | to_string 10 | end 11 | 12 | def inspect 13 | # Overwrite inspect because Object#inspect calls to_s. In case 14 | # of an unlinked frame, calling to_s would lead to calling into 15 | # SWIG through to_string, which in turn would raise an exception 16 | # with a message including an inspect of this object, which 17 | # would call to_s -> stack overflow. 18 | "#<%s:0x%x>" % [self.class.to_s, object_id << 1] 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/taglib/mp4.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_mp4' 4 | 5 | module TagLib::MP4 6 | class File 7 | extend ::TagLib::FileOpenable 8 | end 9 | 10 | class Tag 11 | remove_method :save 12 | end 13 | 14 | class Item 15 | def self.from_int_pair(ary) 16 | raise ArgumentError, 'argument should be an array' unless ary.is_a? Array 17 | raise ArgumentError, 'argument should have exactly two elements' if ary.length != 2 18 | 19 | new(*ary) 20 | end 21 | end 22 | 23 | class ItemMap 24 | alias clear _clear 25 | alias insert _insert 26 | alias [] fetch 27 | alias []= insert 28 | remove_method :_clear 29 | remove_method :_insert 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/taglib/mpeg.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_mpeg' 4 | 5 | module TagLib::MPEG 6 | class File 7 | extend ::TagLib::FileOpenable 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/taglib/ogg.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_ogg' 4 | -------------------------------------------------------------------------------- /lib/taglib/version.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | module TagLib 4 | module Version 5 | MAJOR = 2 6 | MINOR = 0 7 | PATCH = 0 8 | BUILD = nil 9 | 10 | STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.') 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/taglib/vorbis.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_vorbis' 4 | 5 | module TagLib::Ogg::Vorbis 6 | class File 7 | extend ::TagLib::FileOpenable 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/taglib/wav.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'taglib_wav' 4 | 5 | module TagLib::RIFF::WAV 6 | FORMAT_UNKNOWN = 0x0000 7 | FORMAT_PCM = 0x0001 8 | 9 | class File 10 | extend ::TagLib::FileOpenable 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /taglib-ruby.gemspec: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | $LOAD_PATH.push File.expand_path('lib', __dir__) 4 | 5 | require 'taglib/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = 'taglib-ruby' 9 | s.version = TagLib::Version::STRING 10 | s.authors = ['Robin Stocker', 'Jacob Vosmaer', 'Thomas Chevereau'] 11 | s.email = ['robin@nibor.org'] 12 | s.homepage = 'http://robinst.github.io/taglib-ruby/' 13 | s.licenses = ['MIT'] 14 | s.summary = 'Ruby interface for the taglib C++ library' 15 | s.description = <<~DESC 16 | Ruby interface for the taglib C++ library, for reading and writing 17 | meta-data (tags) of many audio formats. 18 | 19 | In contrast to other libraries, this one wraps the C++ API using SWIG, 20 | not only the minimal C API. This means that all tags can be accessed. 21 | DESC 22 | 23 | s.require_paths = ['lib'] 24 | s.requirements = ['taglib (libtag1-dev in Debian/Ubuntu, taglib-devel in Fedora/RHEL)'] 25 | 26 | s.add_development_dependency 'bundler', '>= 1.2', '< 3' 27 | s.add_development_dependency 'kramdown', '~> 2.3.0' 28 | s.add_development_dependency 'rake-compiler', '~> 0.9' 29 | s.add_development_dependency 'shoulda-context', '~> 2.0' 30 | s.add_development_dependency 'test-unit', '~> 3.5' 31 | s.add_development_dependency 'yard', '~> 0.9.26' 32 | 33 | s.extensions = [ 34 | 'ext/taglib_base/extconf.rb', 35 | 'ext/taglib_mpeg/extconf.rb', 36 | 'ext/taglib_id3v1/extconf.rb', 37 | 'ext/taglib_id3v2/extconf.rb', 38 | 'ext/taglib_ogg/extconf.rb', 39 | 'ext/taglib_vorbis/extconf.rb', 40 | 'ext/taglib_flac/extconf.rb', 41 | 'ext/taglib_flac_picture/extconf.rb', 42 | 'ext/taglib_mp4/extconf.rb', 43 | 'ext/taglib_aiff/extconf.rb', 44 | 'ext/taglib_wav/extconf.rb' 45 | ] 46 | s.extra_rdoc_files = [ 47 | 'CHANGELOG.md', 48 | 'LICENSE.txt', 49 | 'README.md' 50 | ] 51 | s.files = [ 52 | '.github/FUNDING.yml', 53 | '.rubocop.yml', 54 | '.yardopts', 55 | 'CHANGELOG.md', 56 | 'Gemfile', 57 | 'Guardfile', 58 | 'LICENSE.txt', 59 | 'README.md', 60 | 'Rakefile', 61 | 'docs/default/fulldoc/html/css/common.css', 62 | 'docs/taglib/aiff.rb', 63 | 'docs/taglib/base.rb', 64 | 'docs/taglib/flac.rb', 65 | 'docs/taglib/id3v1.rb', 66 | 'docs/taglib/id3v2.rb', 67 | 'docs/taglib/mp4.rb', 68 | 'docs/taglib/mpeg.rb', 69 | 'docs/taglib/ogg.rb', 70 | 'docs/taglib/riff.rb', 71 | 'docs/taglib/vorbis.rb', 72 | 'docs/taglib/wav.rb', 73 | 'ext/extconf_common.rb', 74 | 'ext/taglib_aiff/extconf.rb', 75 | 'ext/taglib_aiff/taglib_aiff.i', 76 | 'ext/taglib_aiff/taglib_aiff_wrap.cxx', 77 | 'ext/taglib_base/extconf.rb', 78 | 'ext/taglib_base/includes.i', 79 | 'ext/taglib_base/taglib_base.i', 80 | 'ext/taglib_base/taglib_base_wrap.cxx', 81 | 'ext/taglib_flac/extconf.rb', 82 | 'ext/taglib_flac/taglib_flac.i', 83 | 'ext/taglib_flac/taglib_flac_wrap.cxx', 84 | 'ext/taglib_flac_picture/extconf.rb', 85 | 'ext/taglib_flac_picture/includes.i', 86 | 'ext/taglib_flac_picture/taglib_flac_picture.i', 87 | 'ext/taglib_flac_picture/taglib_flac_picture_wrap.cxx', 88 | 'ext/taglib_id3v1/extconf.rb', 89 | 'ext/taglib_id3v1/taglib_id3v1.i', 90 | 'ext/taglib_id3v1/taglib_id3v1_wrap.cxx', 91 | 'ext/taglib_id3v2/extconf.rb', 92 | 'ext/taglib_id3v2/relativevolumeframe.i', 93 | 'ext/taglib_id3v2/taglib_id3v2.i', 94 | 'ext/taglib_id3v2/taglib_id3v2_wrap.cxx', 95 | 'ext/taglib_mp4/extconf.rb', 96 | 'ext/taglib_mp4/taglib_mp4.i', 97 | 'ext/taglib_mp4/taglib_mp4_wrap.cxx', 98 | 'ext/taglib_mpeg/extconf.rb', 99 | 'ext/taglib_mpeg/taglib_mpeg.i', 100 | 'ext/taglib_mpeg/taglib_mpeg_wrap.cxx', 101 | 'ext/taglib_ogg/extconf.rb', 102 | 'ext/taglib_ogg/taglib_ogg.i', 103 | 'ext/taglib_ogg/taglib_ogg_wrap.cxx', 104 | 'ext/taglib_vorbis/extconf.rb', 105 | 'ext/taglib_vorbis/taglib_vorbis.i', 106 | 'ext/taglib_vorbis/taglib_vorbis_wrap.cxx', 107 | 'ext/taglib_wav/extconf.rb', 108 | 'ext/taglib_wav/taglib_wav.i', 109 | 'ext/taglib_wav/taglib_wav_wrap.cxx', 110 | 'ext/valgrind-suppressions.txt', 111 | 'ext/win.cmake', 112 | 'lib/taglib.rb', 113 | 'lib/taglib/aiff.rb', 114 | 'lib/taglib/base.rb', 115 | 'lib/taglib/flac.rb', 116 | 'lib/taglib/id3v1.rb', 117 | 'lib/taglib/id3v2.rb', 118 | 'lib/taglib/mp4.rb', 119 | 'lib/taglib/mpeg.rb', 120 | 'lib/taglib/ogg.rb', 121 | 'lib/taglib/version.rb', 122 | 'lib/taglib/vorbis.rb', 123 | 'lib/taglib/wav.rb', 124 | 'taglib-ruby.gemspec', 125 | 'tasks/docs_coverage.rake', 126 | 'tasks/ext.rake', 127 | 'tasks/gemspec_check.rake', 128 | 'tasks/swig.rake', 129 | 'test/aiff_examples_test.rb', 130 | 'test/aiff_file_test.rb', 131 | 'test/aiff_file_write_test.rb', 132 | 'test/base_test.rb', 133 | 'test/data/Makefile', 134 | 'test/data/add-relative-volume.cpp', 135 | 'test/data/aiff-sample.aiff', 136 | 'test/data/crash.mp3', 137 | 'test/data/flac-create.cpp', 138 | 'test/data/flac.flac', 139 | 'test/data/flac_nopic.flac', 140 | 'test/data/get_picture_data.cpp', 141 | 'test/data/globe_east_540.jpg', 142 | 'test/data/globe_east_90.jpg', 143 | 'test/data/id3v1-create.cpp', 144 | 'test/data/id3v1.mp3', 145 | 'test/data/mp4-create.cpp', 146 | 'test/data/mp4.m4a', 147 | 'test/data/relative-volume.mp3', 148 | 'test/data/sample.mp3', 149 | 'test/data/unicode.mp3', 150 | 'test/data/vorbis-create.cpp', 151 | 'test/data/vorbis.oga', 152 | 'test/data/wav-create.cpp', 153 | 'test/data/wav-dump.cpp', 154 | 'test/data/wav-sample.wav', 155 | 'test/file_test.rb', 156 | 'test/fileref_open_test.rb', 157 | 'test/fileref_properties_test.rb', 158 | 'test/fileref_write_test.rb', 159 | 'test/flac_file_test.rb', 160 | 'test/flac_file_write_test.rb', 161 | 'test/flac_picture_memory_test.rb', 162 | 'test/helper.rb', 163 | 'test/id3v1_genres_test.rb', 164 | 'test/id3v1_tag_test.rb', 165 | 'test/id3v2_frames_test.rb', 166 | 'test/id3v2_header_test.rb', 167 | 'test/id3v2_memory_test.rb', 168 | 'test/id3v2_relative_volume_test.rb', 169 | 'test/id3v2_tag_test.rb', 170 | 'test/id3v2_unicode_test.rb', 171 | 'test/id3v2_unknown_frames_test.rb', 172 | 'test/id3v2_write_test.rb', 173 | 'test/mp4_file_test.rb', 174 | 'test/mp4_file_write_test.rb', 175 | 'test/mp4_items_test.rb', 176 | 'test/mpeg_file_test.rb', 177 | 'test/tag_test.rb', 178 | 'test/unicode_filename_test.rb', 179 | 'test/vorbis_file_test.rb', 180 | 'test/vorbis_tag_test.rb', 181 | 'test/wav_examples_test.rb', 182 | 'test/wav_file_test.rb', 183 | 'test/wav_file_write_test.rb' 184 | ] 185 | end 186 | -------------------------------------------------------------------------------- /tasks/build.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | class Build 4 | class << self 5 | def plat 6 | ENV['PLATFORM'] || 'i386-mingw32' 7 | end 8 | 9 | def version 10 | ENV['TAGLIB_VERSION'] || '2.0.1' 11 | end 12 | 13 | def dir 14 | "taglib-#{version}" 15 | end 16 | 17 | def tmp 18 | "#{__dir__}/../tmp" 19 | end 20 | 21 | def source 22 | "#{tmp}/#{dir}" 23 | end 24 | 25 | def tmp_arch 26 | "#{tmp}/#{plat}" 27 | end 28 | 29 | def install_dir 30 | "#{tmp_arch}/#{dir}" 31 | end 32 | 33 | def build_dir 34 | "#{install_dir}-build" 35 | end 36 | 37 | def library 38 | "#{install_dir}/lib/libtag.#{RbConfig::CONFIG['SOEXT']}" 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /tasks/docs_coverage.rake: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | desc 'Show the coverage of the actual API in ext/ by the API docs in docs/' 4 | task :docs_coverage do |_t| 5 | def sort(objects) 6 | objects.sort_by(&:path) 7 | end 8 | 9 | def synthetic_methods(cls) 10 | methods = cls.inherited_meths 11 | methods.map do |m| 12 | YARD::CodeObjects::MethodObject.new(cls, m.name) 13 | end 14 | end 15 | 16 | YARD.parse('ext/**/*_wrap.cxx') 17 | ext_codeobjects = sort(YARD::Registry.all) 18 | 19 | YARD::Registry.clear 20 | YARD.parse('docs/**/*.rb') 21 | docs_classes = YARD::Registry.all(:class) 22 | docs_others = YARD::Registry.all(:module, :method) 23 | docs_codeobjects = sort(docs_classes + docs_others) 24 | 25 | # SWIG generates methods for base classes in the subclasses too, we 26 | # don't want them appearing in the 'only in ext' list. 27 | inherited_methods = docs_classes.map { |c| synthetic_methods(c) }.flatten 28 | 29 | only_in_ext = (ext_codeobjects - docs_codeobjects - inherited_methods) 30 | only_in_docs = (docs_codeobjects - ext_codeobjects) 31 | 32 | unless only_in_ext.empty? 33 | puts 34 | puts '=== Only in ext (to document): ' 35 | puts only_in_ext 36 | p only_in_ext[4].explicit 37 | end 38 | unless only_in_docs.empty? 39 | puts 40 | puts '=== Only in docs (typo?): ' 41 | puts only_in_docs 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /tasks/ext.rake: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require_relative 'build' 4 | 5 | # Extension tasks and cross-compiling 6 | 7 | host = 'i686-w64-mingw32' 8 | toolchain_file = "#{Dir.pwd}/ext/win.cmake" 9 | install_dll = "#{Build.install_dir}/bin/libtag.dll" 10 | $cross_config_options = ["--with-opt-dir=#{Build.install_dir}"] 11 | 12 | taglib_url = "https://github.com/taglib/taglib/archive/v#{Build.version}.tar.gz" 13 | taglib_options = ['-DCMAKE_BUILD_TYPE=Release', 14 | '-DBUILD_EXAMPLES=OFF', 15 | '-DBUILD_TESTS=OFF', 16 | '-DBUILD_TESTING=OFF', # used since 1.13 instead of BUILD_TESTS 17 | '-DBUILD_BINDINGS=OFF', # 1.11 builds bindings by default 18 | '-DBUILD_SHARED_LIBS=ON', # 1.11 builds static by default 19 | '-DWITH_MP4=ON', # WITH_MP4, WITH_ASF only needed with taglib 1.7, will be default in 1.8 20 | '-DWITH_ASF=ON'].join(' ') 21 | 22 | def configure_cross_compile(ext) 23 | ext.cross_compile = true 24 | ext.cross_platform = Build.plat 25 | ext.cross_config_options.concat($cross_config_options) 26 | ext.cross_compiling do |gem| 27 | gem.files << 'lib/libtag.dll' 28 | end 29 | end 30 | 31 | require 'rake/extensiontask' 32 | Rake::ExtensionTask.new('taglib_base', $gemspec) do |ext| 33 | configure_cross_compile(ext) 34 | end 35 | Rake::ExtensionTask.new('taglib_mpeg', $gemspec) do |ext| 36 | configure_cross_compile(ext) 37 | end 38 | Rake::ExtensionTask.new('taglib_id3v1', $gemspec) do |ext| 39 | configure_cross_compile(ext) 40 | end 41 | Rake::ExtensionTask.new('taglib_id3v2', $gemspec) do |ext| 42 | configure_cross_compile(ext) 43 | end 44 | Rake::ExtensionTask.new('taglib_ogg', $gemspec) do |ext| 45 | configure_cross_compile(ext) 46 | end 47 | Rake::ExtensionTask.new('taglib_vorbis', $gemspec) do |ext| 48 | configure_cross_compile(ext) 49 | end 50 | Rake::ExtensionTask.new('taglib_flac_picture', $gemspec) do |ext| 51 | configure_cross_compile(ext) 52 | end 53 | Rake::ExtensionTask.new('taglib_flac', $gemspec) do |ext| 54 | configure_cross_compile(ext) 55 | end 56 | Rake::ExtensionTask.new('taglib_mp4', $gemspec) do |ext| 57 | configure_cross_compile(ext) 58 | end 59 | Rake::ExtensionTask.new('taglib_aiff', $gemspec) do |ext| 60 | configure_cross_compile(ext) 61 | end 62 | Rake::ExtensionTask.new('taglib_wav', $gemspec) do |ext| 63 | configure_cross_compile(ext) 64 | end 65 | 66 | task :cross do 67 | # Mkmf just uses "g++" as C++ compiler, despite what's in rbconfig.rb. 68 | # So, we need to hack around it by setting CXX to the cross compiler. 69 | ENV['CXX'] = "#{host}-g++" 70 | end 71 | 72 | file "#{Build.tmp_arch}/stage/lib/libtag.dll" => [install_dll] do |f| 73 | install install_dll, f 74 | end 75 | 76 | file install_dll => [Build.source] do 77 | chdir Build.source do 78 | sh %(cmake -DCMAKE_INSTALL_PREFIX=#{Build.install_dir} -DCMAKE_TOOLCHAIN_FILE=#{toolchain_file} #{taglib_options}) 79 | sh 'make VERBOSE=1' 80 | sh 'make install' 81 | end 82 | end 83 | 84 | task vendor: [Build.library] 85 | 86 | file Build.library => [Build.install_dir, Build.build_dir, Build.source] do 87 | chdir Build.build_dir do 88 | sh %(cmake -DCMAKE_INSTALL_PREFIX=#{Build.install_dir} #{taglib_options} #{Build.source}) 89 | sh 'make install -j 4 VERBOSE=1' 90 | end 91 | end 92 | 93 | directory Build.install_dir 94 | directory Build.build_dir 95 | directory Build.tmp 96 | 97 | file Build.source do 98 | sh "git clone --depth=1 --branch=v#{Build.version} https://github.com/taglib/taglib.git #{Build.source}" 99 | sh "git -C #{Build.source} submodule init" 100 | sh "git -C #{Build.source} submodule update --depth=1" 101 | end 102 | -------------------------------------------------------------------------------- /tasks/gemspec_check.rake: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | desc 'Checks file list in .gemspec against files tracked in Git' 4 | task :gemspec_check do |_t| 5 | exclude = ['.gitignore', '.github/workflows/ci.yml'] 6 | git_files = `git ls-files`.split("\n") - exclude 7 | gemspec_files = $gemspec.files 8 | 9 | only_in_gemspec = gemspec_files - git_files 10 | only_in_git = git_files - gemspec_files 11 | 12 | unless only_in_gemspec.empty? 13 | puts 'In gemspec but not in git:' 14 | puts only_in_gemspec 15 | end 16 | 17 | unless only_in_git.empty? 18 | puts 'In git but not in gemspec:' 19 | puts only_in_git 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /tasks/swig.rake: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require_relative 'build' 4 | 5 | # Tasks for generating SWIG wrappers in ext 6 | 7 | # Execute SWIG for the specified extension. 8 | # Arguments: 9 | # mod:: The name of the SWIG wrapper to process. 10 | # 11 | # If the TAGLIB_DIR environment variable points to a directory, 12 | # $TAGLIB_DIR/include will be searched first for taglib headers. 13 | def run_swig(mod) 14 | swig = `which swig`.chomp 15 | swig = `which swig2.0`.chomp if swig.empty? 16 | swiglib = `swig -swiglib`.chomp 17 | abort 'swig failed' unless $?.success? 18 | 19 | # Standard search location for headers 20 | include_args = "-I#{Build.install_dir}/include" 21 | 22 | if ENV.key?('TAGLIB_DIR') 23 | unless File.directory?(ENV['TAGLIB_DIR']) 24 | abort 'When defined, the TAGLIB_DIR environment variable must point to a valid directory.' 25 | end 26 | 27 | # Push it in front to get it searched first. 28 | include_args = "-I#{ENV['TAGLIB_DIR']}/include" 29 | end 30 | 31 | Dir.chdir("ext/#{mod}") do 32 | sh "#{swig} -c++ -ruby -autorename -initname #{mod} #{include_args} #{mod}.i" 33 | wrap = "#{mod}_wrap.cxx" 34 | wrapdata = File.read(wrap) 35 | File.write(wrap, wrapdata.gsub(swiglib, '/swig')) 36 | end 37 | end 38 | 39 | task swig: 40 | [Build.library, 41 | 'ext/taglib_base/taglib_base_wrap.cxx', 42 | 'ext/taglib_mpeg/taglib_mpeg_wrap.cxx', 43 | 'ext/taglib_id3v1/taglib_id3v1_wrap.cxx', 44 | 'ext/taglib_id3v2/taglib_id3v2_wrap.cxx', 45 | 'ext/taglib_ogg/taglib_ogg_wrap.cxx', 46 | 'ext/taglib_vorbis/taglib_vorbis_wrap.cxx', 47 | 'ext/taglib_flac/taglib_flac_picture_wrap.cxx', 48 | 'ext/taglib_flac/taglib_flac_wrap.cxx', 49 | 'ext/taglib_mp4/taglib_mp4_wrap.cxx', 50 | 'ext/taglib_aiff/taglib_aiff_wrap.cxx', 51 | 'ext/taglib_wav/taglib_wav_wrap.cxx'] 52 | 53 | base_dependencies = ['ext/taglib_base/taglib_base.i', 'ext/taglib_base/includes.i'] 54 | 55 | file 'ext/taglib_base/taglib_base_wrap.cxx' => base_dependencies do 56 | run_swig('taglib_base') 57 | end 58 | 59 | file 'ext/taglib_mpeg/taglib_mpeg_wrap.cxx' => ['ext/taglib_mpeg/taglib_mpeg.i'] + base_dependencies do 60 | run_swig('taglib_mpeg') 61 | end 62 | 63 | file 'ext/taglib_id3v1/taglib_id3v1_wrap.cxx' => ['ext/taglib_id3v1/taglib_id3v1.i'] + base_dependencies do 64 | run_swig('taglib_id3v1') 65 | end 66 | 67 | file 'ext/taglib_id3v2/taglib_id3v2_wrap.cxx' => ['ext/taglib_id3v2/taglib_id3v2.i'] + base_dependencies do 68 | run_swig('taglib_id3v2') 69 | end 70 | 71 | file 'ext/taglib_ogg/taglib_ogg_wrap.cxx' => ['ext/taglib_ogg/taglib_ogg.i'] + base_dependencies do 72 | run_swig('taglib_ogg') 73 | end 74 | 75 | file 'ext/taglib_vorbis/taglib_vorbis_wrap.cxx' => ['ext/taglib_vorbis/taglib_vorbis.i'] + base_dependencies do 76 | run_swig('taglib_vorbis') 77 | end 78 | 79 | file 'ext/taglib_flac/taglib_flac_picture_wrap.cxx' => ['ext/taglib_flac_picture/taglib_flac_picture.i'] + base_dependencies do 80 | run_swig('taglib_flac_picture') 81 | end 82 | 83 | file 'ext/taglib_flac/taglib_flac_wrap.cxx' => ['ext/taglib_flac/taglib_flac.i', 'ext/taglib_flac_picture/taglib_flac_picture.i'] + base_dependencies do 84 | run_swig('taglib_flac') 85 | end 86 | 87 | file 'ext/taglib_mp4/taglib_mp4_wrap.cxx' => ['ext/taglib_mp4/taglib_mp4.i'] + base_dependencies do 88 | run_swig('taglib_mp4') 89 | end 90 | 91 | file 'ext/taglib_aiff/taglib_aiff_wrap.cxx' => ['ext/taglib_aiff/taglib_aiff.i'] + base_dependencies do 92 | run_swig('taglib_aiff') 93 | end 94 | 95 | file 'ext/taglib_wav/taglib_wav_wrap.cxx' => ['ext/taglib_wav/taglib_wav.i'] + base_dependencies do 96 | run_swig('taglib_wav') 97 | end 98 | -------------------------------------------------------------------------------- /test/aiff_examples_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class AIFFExamples < Test::Unit::TestCase 6 | DATA_FILE_PREFIX = 'test/data/aiff-' 7 | 8 | context 'TagLib::RIFF::AIFF::File' do 9 | should 'Run TagLib::RIFF::AIFF::File examples' do 10 | # @example Reading the title 11 | title = TagLib::RIFF::AIFF::File.open("#{DATA_FILE_PREFIX}sample.aiff") do |file| 12 | file.tag.title 13 | end 14 | 15 | # @example Reading AIFF-specific audio properties 16 | TagLib::RIFF::AIFF::File.open("#{DATA_FILE_PREFIX}sample.aiff") do |file| 17 | file.audio_properties.bits_per_sample # => 16 18 | end 19 | 20 | # @example Saving ID3v2 cover-art to disk 21 | TagLib::RIFF::AIFF::File.open("#{DATA_FILE_PREFIX}sample.aiff") do |file| 22 | id3v2_tag = file.tag 23 | cover = id3v2_tag.frame_list('APIC').first 24 | ext = cover.mime_type.rpartition('/')[2] 25 | File.open("#{DATA_FILE_PREFIX}cover-art.#{ext}", 'wb') { |f| f.write cover.picture } 26 | end 27 | 28 | # checks 29 | assert_equal 'AIFF Dummy Track Title - ID3v2.4', title 30 | assert_equal true, File.exist?("#{DATA_FILE_PREFIX}cover-art.jpeg") 31 | FileUtils.rm("#{DATA_FILE_PREFIX}cover-art.jpeg") 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/aiff_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class AIFFFileTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/aiff-sample.aiff' 7 | PICTURE_FILE = 'test/data/globe_east_540.jpg' 8 | 9 | context 'TagLib::RIFF::AIFF::File' do 10 | setup do 11 | @file = TagLib::RIFF::AIFF::File.new(SAMPLE_FILE) 12 | @tag = @file.tag 13 | File.open(PICTURE_FILE, 'rb') do |f| 14 | @picture_data = f.read 15 | end 16 | end 17 | 18 | should 'have an ID3v2 tag' do 19 | assert @file.id3v2_tag? 20 | refute_nil @tag 21 | assert_equal TagLib::ID3v2::Tag, @tag.class 22 | end 23 | 24 | should 'contain basic tag information' do 25 | assert_equal 'AIFF Dummy Track Title - ID3v2.4', @tag.title 26 | assert_equal 'AIFF Dummy Artist Name', @tag.artist 27 | assert_equal 'AIFF Dummy Album Title', @tag.album 28 | assert_equal 'AIFF Dummy Comment', @tag.comment 29 | assert_equal 'Jazz', @tag.genre 30 | assert_equal 2014, @tag.year 31 | assert_equal 3, @tag.track 32 | assert_equal false, @tag.empty? 33 | end 34 | 35 | context 'APIC frame' do 36 | setup do 37 | @apic = @tag.frame_list('APIC').first 38 | end 39 | 40 | should 'exist' do 41 | assert_not_nil @apic 42 | assert_equal TagLib::ID3v2::AttachedPictureFrame, @apic.class 43 | end 44 | 45 | should 'have a type' do 46 | assert_equal TagLib::ID3v2::AttachedPictureFrame::FrontCover, @apic.type 47 | end 48 | 49 | should 'have a mime type' do 50 | assert_equal 'image/jpeg', @apic.mime_type 51 | end 52 | 53 | should 'have picture bytes' do 54 | assert_equal 61649, @apic.picture.size 55 | assert_equal @picture_data, @apic.picture 56 | end 57 | end 58 | 59 | context 'audio properties' do 60 | setup do 61 | @properties = @file.audio_properties 62 | end 63 | 64 | should 'exist' do 65 | assert_not_nil @properties 66 | end 67 | 68 | should 'contain basic information' do 69 | assert_equal 2, @properties.length_in_seconds 70 | assert_equal 2937, @properties.length_in_milliseconds 71 | assert_equal 256, @properties.bitrate 72 | assert_equal 8000, @properties.sample_rate 73 | assert_equal 2, @properties.channels 74 | end 75 | 76 | should 'contain AIFF-specific information' do 77 | assert_equal 16, @properties.bits_per_sample 78 | assert_equal 23493, @properties.sample_frames 79 | end 80 | 81 | should 'do not contain AIFF-C information' do 82 | refute @properties.aiff_c? 83 | assert_equal '', @properties.compression_type 84 | assert_equal '', @properties.compression_name 85 | end 86 | end 87 | 88 | teardown do 89 | @file.close 90 | @file = nil 91 | end 92 | end 93 | 94 | context 'TagLib::RIFF::AIFF::File.open' do 95 | should 'have open method' do 96 | title = nil 97 | TagLib::RIFF::AIFF::File.open(SAMPLE_FILE, false) do |file| 98 | title = file.tag.title 99 | end 100 | assert_equal 'AIFF Dummy Track Title - ID3v2.4', title 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /test/aiff_file_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class AIFFFileWriteTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/aiff-sample.aiff' 7 | OUTPUT_FILE = 'test/data/_output.aiff' 8 | PICTURE_FILE = 'test/data/globe_east_90.jpg' 9 | 10 | def reloaded(&block) 11 | TagLib::RIFF::AIFF::File.open(OUTPUT_FILE, false, &block) 12 | end 13 | 14 | context 'TagLib::RIFF::AIFF::File' do 15 | setup do 16 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 17 | @file = TagLib::RIFF::AIFF::File.new(OUTPUT_FILE, false) 18 | end 19 | 20 | should 'be able to save the title' do 21 | tag = @file.tag 22 | assert_not_nil tag 23 | tag.title = 'New Title' 24 | success = @file.save 25 | assert success 26 | @file.close 27 | @file = nil 28 | 29 | written_title = reloaded do |file| 30 | file.tag.title 31 | end 32 | assert_equal 'New Title', written_title 33 | end 34 | 35 | should 'have one picture frame' do 36 | assert_equal 1, @file.tag.frame_list('APIC').size 37 | end 38 | 39 | should 'be able to remove all picture frames' do 40 | @file.tag.remove_frames('APIC') 41 | success = @file.save 42 | assert success 43 | @file.close 44 | @file = nil 45 | 46 | reloaded do |file| 47 | assert_equal 0, file.tag.frame_list('APIC').size 48 | end 49 | end 50 | 51 | should 'be able to add a picture frame' do 52 | picture_data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 53 | 54 | apic = TagLib::ID3v2::AttachedPictureFrame.new 55 | apic.mime_type = 'image/jpeg' 56 | apic.description = 'desc' 57 | apic.text_encoding = TagLib::String::UTF8 58 | apic.picture = picture_data 59 | apic.type = TagLib::ID3v2::AttachedPictureFrame::BackCover 60 | 61 | @file.tag.add_frame(apic) 62 | success = @file.save 63 | assert success 64 | @file.close 65 | @file = nil 66 | 67 | reloaded do |file| 68 | assert_equal 2, file.tag.frame_list('APIC').size 69 | end 70 | 71 | reloaded do |file| 72 | written_apic = file.tag.frame_list('APIC')[1] 73 | assert_equal 'image/jpeg', written_apic.mime_type 74 | assert_equal 'desc', written_apic.description 75 | assert_equal picture_data, written_apic.picture 76 | end 77 | end 78 | 79 | teardown do 80 | if @file 81 | @file.close 82 | @file = nil 83 | end 84 | FileUtils.rm OUTPUT_FILE 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/base_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class BaseTest < Test::Unit::TestCase 6 | context 'TagLib' do 7 | should 'contain version constants' do 8 | assert TagLib::TAGLIB_MAJOR_VERSION.is_a? Integer 9 | assert TagLib::TAGLIB_MINOR_VERSION.is_a? Integer 10 | assert TagLib::TAGLIB_PATCH_VERSION.is_a? Integer 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/data/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | all:: add-relative-volume id3v1-create vorbis-create flac-create mp4-create wav-create wav-dump 4 | 5 | add-relative-volume: add-relative-volume.cpp 6 | g++ -o $@ $< -ltag -I/usr/include/taglib 7 | 8 | id3v1-create: id3v1-create.cpp 9 | g++ -o $@ $< -ltag -I/usr/include/taglib 10 | 11 | vorbis-create: vorbis-create.cpp 12 | g++ -o $@ $< -ltag -I/usr/include/taglib 13 | 14 | flac-create: flac-create.cpp 15 | g++ -o $@ $< -ltag -I/usr/include/taglib 16 | 17 | mp4-create: mp4-create.cpp 18 | g++ -o $@ $< -ltag -I/usr/include/taglib 19 | 20 | wav-create: wav-create.cpp 21 | g++ -o $@ $< -ltag -I/usr/include/taglib 22 | 23 | wav-dump: wav-dump.cpp 24 | g++ -o $@ $< -ltag -I/usr/include/taglib 25 | 26 | clean:: 27 | -rm add-relative-volume id3v1-create vorbis-create flac-create mp4-create wav-create wav-dump 28 | -------------------------------------------------------------------------------- /test/data/add-relative-volume.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace TagLib; 10 | 11 | int main(int argc, char **argv) { 12 | if (argc != 2) { 13 | std::cerr << "usage: " << argv[0] << " file.mp3" << std::endl; 14 | return EXIT_FAILURE; 15 | } 16 | char *filename = argv[1]; 17 | 18 | MPEG::File file(filename); 19 | ID3v2::Tag *tag = file.ID3v2Tag(true); 20 | 21 | ID3v2::RelativeVolumeFrame *rv = new ID3v2::RelativeVolumeFrame(); 22 | 23 | rv->setVolumeAdjustmentIndex(512); 24 | rv->setVolumeAdjustmentIndex(1024, ID3v2::RelativeVolumeFrame::Subwoofer); 25 | 26 | ID3v2::RelativeVolumeFrame::PeakVolume pv1; 27 | pv1.bitsRepresentingPeak = 8; 28 | pv1.peakVolume = "A"; // 0x41, 0b01000001 29 | rv->setPeakVolume(pv1); 30 | 31 | ID3v2::RelativeVolumeFrame::PeakVolume pv2; 32 | pv2.bitsRepresentingPeak = 4; 33 | pv2.peakVolume = "?"; // 0x3F, 0b00111111 34 | rv->setPeakVolume(pv2, ID3v2::RelativeVolumeFrame::Subwoofer); 35 | 36 | tag->addFrame(rv); 37 | file.save(); 38 | 39 | delete rv; 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | 44 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 45 | -------------------------------------------------------------------------------- /test/data/aiff-sample.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/aiff-sample.aiff -------------------------------------------------------------------------------- /test/data/crash.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/crash.mp3 -------------------------------------------------------------------------------- /test/data/flac-create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "get_picture_data.cpp" 9 | 10 | using namespace TagLib; 11 | 12 | int main(int argc, char **argv) { 13 | if (argc != 2) { 14 | std::cerr << "usage: " << argv[0] << " file" << std::endl; 15 | return EXIT_FAILURE; 16 | } 17 | 18 | char *filename = argv[1]; 19 | 20 | FLAC::File file(filename); 21 | Ogg::XiphComment *tag = file.xiphComment(true); 22 | 23 | tag->setTitle("Title"); 24 | tag->setArtist("Artist"); 25 | tag->setAlbum("Album"); 26 | tag->setComment("Comment"); 27 | tag->setGenre("Pop"); 28 | tag->setYear(2011); 29 | tag->setTrack(7); 30 | 31 | tag->addField("VERSION", "original"); 32 | tag->addField("PERFORMER", "Performer"); 33 | tag->addField("COPYRIGHT", "2011 Me, myself and I"); 34 | tag->addField("LICENSE", "Any Use Permitted"); 35 | tag->addField("ORGANIZATION", "Organization"); 36 | tag->addField("DESCRIPTION", "Test file"); 37 | tag->addField("LOCATION", "Earth"); 38 | tag->addField("CONTACT", "Contact"); 39 | 40 | tag->addField("MULTIPLE", "A"); 41 | tag->addField("MULTIPLE", "B", false); 42 | 43 | FLAC::Picture *picture = new FLAC::Picture(); 44 | 45 | picture->setType(FLAC::Picture::FrontCover); 46 | picture->setMimeType("image/jpeg"); 47 | picture->setDescription("Globe"); 48 | picture->setWidth(90); 49 | picture->setHeight(90); 50 | picture->setColorDepth(8); 51 | picture->setNumColors(0); 52 | 53 | const ByteVector data = getPictureData("globe_east_90.jpg"); 54 | if (data.isEmpty()) { 55 | std::cerr << "failed to get picture data" << std::endl; 56 | delete picture; 57 | return EXIT_FAILURE; 58 | } 59 | 60 | picture->setData(data); 61 | 62 | file.addPicture(picture); 63 | file.save(); 64 | 65 | delete picture; 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | 70 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 71 | -------------------------------------------------------------------------------- /test/data/flac.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/flac.flac -------------------------------------------------------------------------------- /test/data/flac_nopic.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/flac_nopic.flac -------------------------------------------------------------------------------- /test/data/get_picture_data.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace TagLib; 4 | 5 | ByteVector getPictureData(const char *filename) { 6 | std::ifstream is; 7 | is.open(filename, std::ios::binary); 8 | if (!is.is_open()) { 9 | std::cerr << "failed to open file: " << filename << std::endl; 10 | return ByteVector(); 11 | } 12 | 13 | is.seekg(0, std::ios::end); 14 | const int length = is.tellg(); 15 | is.seekg(0, std::ios::beg); 16 | 17 | char *buffer = new char[length]; 18 | 19 | is.read(buffer, length); 20 | is.close(); 21 | 22 | ByteVector result(buffer, length); 23 | delete[] buffer; 24 | 25 | return result; 26 | } 27 | -------------------------------------------------------------------------------- /test/data/globe_east_540.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/globe_east_540.jpg -------------------------------------------------------------------------------- /test/data/globe_east_90.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/globe_east_90.jpg -------------------------------------------------------------------------------- /test/data/id3v1-create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace TagLib; 9 | 10 | int main(int argc, char **argv) { 11 | if (argc != 2) { 12 | std::cerr << "usage: " << argv[0] << " file.mp3" << std::endl; 13 | return EXIT_FAILURE; 14 | } 15 | 16 | char *filename = argv[1]; 17 | 18 | MPEG::File file(filename); 19 | ID3v1::Tag *tag = file.ID3v1Tag(true); 20 | 21 | tag->setTitle("Title"); 22 | tag->setArtist("Artist"); 23 | tag->setAlbum("Album"); 24 | tag->setComment("Comment"); 25 | tag->setGenre("Pop"); 26 | tag->setYear(2011); 27 | tag->setTrack(7); 28 | 29 | file.save(MPEG::File::ID3v1); 30 | 31 | return EXIT_SUCCESS; 32 | } 33 | 34 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 35 | -------------------------------------------------------------------------------- /test/data/id3v1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/id3v1.mp3 -------------------------------------------------------------------------------- /test/data/mp4-create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "get_picture_data.cpp" 8 | 9 | using namespace TagLib; 10 | 11 | int main(int argc, char **argv) { 12 | if (argc != 2) { 13 | std::cerr << "usage: " << argv[0] << " file.m4a" << std::endl; 14 | return EXIT_FAILURE; 15 | } 16 | 17 | char *filename = argv[1]; 18 | 19 | MP4::File file(filename); 20 | MP4::Tag *tag = file.tag(); 21 | 22 | tag->setTitle("Title"); 23 | tag->setArtist("Artist"); 24 | tag->setAlbum("Album"); 25 | tag->setComment("Comment"); 26 | tag->setGenre("Pop"); 27 | tag->setYear(2011); 28 | tag->setTrack(7); 29 | 30 | const ByteVector data = getPictureData("globe_east_90.jpg"); 31 | if (data.isEmpty()) { 32 | std::cerr << "failed to get picture data" << std::endl; 33 | return EXIT_FAILURE; 34 | } 35 | 36 | MP4::CoverArt cover_art = MP4::CoverArt(MP4::CoverArt::JPEG, data); 37 | MP4::CoverArtList cover_art_list = MP4::CoverArtList(); 38 | cover_art_list.append(cover_art); 39 | tag->itemListMap().insert("covr", MP4::Item(cover_art_list)); 40 | 41 | file.save(); 42 | 43 | return EXIT_SUCCESS; 44 | } 45 | 46 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 47 | -------------------------------------------------------------------------------- /test/data/mp4.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/mp4.m4a -------------------------------------------------------------------------------- /test/data/relative-volume.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/relative-volume.mp3 -------------------------------------------------------------------------------- /test/data/sample.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/sample.mp3 -------------------------------------------------------------------------------- /test/data/unicode.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/unicode.mp3 -------------------------------------------------------------------------------- /test/data/vorbis-create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "get_picture_data.cpp" 8 | 9 | using namespace TagLib; 10 | 11 | int main(int argc, char **argv) { 12 | if (argc != 2) { 13 | std::cerr << "usage: " << argv[0] << " file.oga" << std::endl; 14 | return EXIT_FAILURE; 15 | } 16 | 17 | char *filename = argv[1]; 18 | 19 | Vorbis::File file(filename); 20 | Ogg::XiphComment *tag = file.tag(); 21 | 22 | tag->removeAllFields(); 23 | tag->removeAllPictures(); 24 | 25 | tag->setTitle("Title"); 26 | tag->setArtist("Artist"); 27 | tag->setAlbum("Album"); 28 | tag->setComment("Comment"); 29 | tag->setGenre("Pop"); 30 | tag->setYear(2011); 31 | tag->setTrack(7); 32 | 33 | tag->addField("VERSION", "original"); 34 | tag->addField("PERFORMER", "Performer"); 35 | tag->addField("COPYRIGHT", "2011 Me, myself and I"); 36 | tag->addField("LICENSE", "Any Use Permitted"); 37 | tag->addField("ORGANIZATION", "Organization"); 38 | tag->addField("DESCRIPTION", "Test file"); 39 | tag->addField("LOCATION", "Earth"); 40 | tag->addField("CONTACT", "Contact"); 41 | 42 | tag->addField("MULTIPLE", "A"); 43 | tag->addField("MULTIPLE", "B", false); 44 | 45 | const ByteVector data = getPictureData("globe_east_90.jpg"); 46 | if (data.isEmpty()) { 47 | std::cerr << "failed to get picture data" << std::endl; 48 | return EXIT_FAILURE; 49 | } 50 | 51 | FLAC::Picture picture; 52 | picture.setType(FLAC::Picture::FrontCover); 53 | picture.setMimeType("image/jpeg"); 54 | picture.setDescription("Globe"); 55 | picture.setWidth(90); 56 | picture.setHeight(90); 57 | picture.setColorDepth(24); 58 | picture.setNumColors(0); 59 | picture.setData(data); 60 | 61 | tag->addField("METADATA_BLOCK_PICTURE", picture.render().toBase64()); 62 | 63 | file.save(); 64 | 65 | return EXIT_SUCCESS; 66 | } 67 | 68 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 69 | -------------------------------------------------------------------------------- /test/data/vorbis.oga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/vorbis.oga -------------------------------------------------------------------------------- /test/data/wav-create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "get_picture_data.cpp" 10 | 11 | using namespace TagLib; 12 | 13 | int main(int argc, char **argv) { 14 | if (argc != 2) { 15 | std::cerr << "usage: " << argv[0] << " file.wav" << std::endl; 16 | return EXIT_FAILURE; 17 | } 18 | 19 | char *filename = argv[1]; 20 | 21 | RIFF::WAV::File file(filename); 22 | ID3v2::Tag *tag = file.tag(); 23 | 24 | tag->setArtist("WAV Dummy Artist Name"); 25 | tag->setAlbum("WAV Dummy Album Title"); 26 | tag->setTitle("WAV Dummy Track Title"); 27 | tag->setTrack(5); 28 | tag->setYear(2014); 29 | tag->setGenre("Jazz"); 30 | tag->setComment("WAV Dummy Comment"); 31 | 32 | tag->removeFrames("APIC"); 33 | 34 | ByteVector picture_data; 35 | ID3v2::AttachedPictureFrame *apic; 36 | 37 | picture_data = getPictureData("globe_east_540.jpg"); 38 | if (picture_data.isEmpty()) { 39 | std::cerr << "failed to get picture data" << std::endl; 40 | return EXIT_FAILURE; 41 | } 42 | 43 | apic = new ID3v2::AttachedPictureFrame(); 44 | apic->setPicture(picture_data); 45 | apic->setMimeType("image/jpeg"); 46 | apic->setType(ID3v2::AttachedPictureFrame::FrontCover); 47 | apic->setDescription("WAV Dummy Front Cover-Art"); 48 | apic->setTextEncoding(String::UTF8); 49 | tag->addFrame(apic); 50 | 51 | picture_data = getPictureData("globe_east_90.jpg"); 52 | if (picture_data.isEmpty()) { 53 | std::cerr << "failed to get picture data" << std::endl; 54 | return EXIT_FAILURE; 55 | } 56 | 57 | apic = new ID3v2::AttachedPictureFrame(); 58 | apic->setPicture(picture_data); 59 | apic->setMimeType("image/jpeg"); 60 | apic->setType(ID3v2::AttachedPictureFrame::Other); 61 | apic->setDescription("WAV Dummy Thumbnail"); 62 | apic->setTextEncoding(String::UTF8); 63 | tag->addFrame(apic); 64 | 65 | file.save(); 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | 70 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 71 | -------------------------------------------------------------------------------- /test/data/wav-dump.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace TagLib; 7 | 8 | 9 | void dump_id3v2(ID3v2::Tag *tag) 10 | { 11 | if (!tag || tag->isEmpty()) 12 | { 13 | std::cout << " NO TAGS" << std::endl; 14 | return; 15 | } 16 | 17 | std::cout << " ID3v2." << tag->header()->majorVersion() << "." << tag->header()->revisionNumber() << std::endl; 18 | std::cout << " title: '" << tag->title() << "'" << std::endl; 19 | std::cout << " artist: '" << tag->artist() << "'" << std::endl; 20 | std::cout << " album: '" << tag->album() << "'" << std::endl; 21 | std::cout << " track: " << tag->track() << std::endl; 22 | std::cout << " year: " << tag->year() << std::endl; 23 | std::cout << " genre: '" << tag->genre() << "'" << std::endl; 24 | std::cout << " comment: '" << tag->comment() << "'" << std::endl; 25 | 26 | std::cout << " Frames" << std::endl; 27 | const ID3v2::FrameList frameList = tag->frameList(); 28 | for(ID3v2::FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); ++it) 29 | std::cout << " " << (*it)->frameID() << " - " << (*it)->toString() << std::endl; 30 | 31 | const ID3v2::FrameList apicList = tag->frameList("APIC"); 32 | for(ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it) 33 | { 34 | const ID3v2::AttachedPictureFrame *apic = static_cast(*it); 35 | std::cout << " Picture" << std::endl; 36 | std::cout << " type: " << apic->type() << std::endl; 37 | std::cout << " mime_type: '" << apic->mimeType() << "'" << std::endl; 38 | std::cout << " description: '" << apic->description() << "'" << std::endl; 39 | std::cout << " size: " << apic->picture().size() << " bytes" << std::endl; 40 | } 41 | } 42 | 43 | void dump_audio_properties(AudioProperties *properties) 44 | { 45 | std::cout << " Audio Properties" << std::endl; 46 | std::cout << " length: " << properties->length() << " (seconds)" << std::endl; 47 | std::cout << " bitrate: " << properties->bitrate() << " (kbits/sec)" << std::endl; 48 | std::cout << " sample_rate: " << properties->sampleRate() << " (Hz)" << std::endl; 49 | std::cout << " channels: " << properties->channels() << std::endl; 50 | } 51 | 52 | void dump_wav_properties(RIFF::WAV::Properties *properties) 53 | { 54 | std::cout << " WAV-specific Properties" << std::endl; 55 | std::cout << " sample_width: " << properties->sampleWidth() << " (bits)" << std::endl; 56 | } 57 | 58 | int main(int argc, char **argv) { 59 | if (argc != 2) { 60 | std::cout << "usage: " << argv[0] << " file.wav" << std::endl; 61 | exit(1); 62 | } 63 | char *filename = argv[1]; 64 | 65 | std::cout << "WAV file '" << filename << "'..." << std::endl; 66 | 67 | RIFF::WAV::File file(filename); 68 | 69 | dump_id3v2(file.tag()); 70 | dump_audio_properties(file.audioProperties()); 71 | dump_wav_properties(file.audioProperties()); 72 | } 73 | 74 | // vim: set filetype=cpp sw=2 ts=2 expandtab: 75 | -------------------------------------------------------------------------------- /test/data/wav-sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robinst/taglib-ruby/9c4f7214f269d78cdc130f4da10faaf2b9332288/test/data/wav-sample.wav -------------------------------------------------------------------------------- /test/file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestFile < Test::Unit::TestCase 6 | context 'The sample.mp3 file' do 7 | setup do 8 | @mpeg_file = TagLib::MPEG::File.new('test/data/sample.mp3', false) 9 | end 10 | 11 | context 'filename' do 12 | should 'be the right name' do 13 | assert_equal 'test/data/sample.mp3', @mpeg_file.name 14 | end 15 | 16 | if HAVE_ENCODING 17 | should 'have the right encoding' do 18 | assert_equal Encoding.find('filesystem'), @mpeg_file.name.encoding 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/fileref_open_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class FileRefOpenTest < Test::Unit::TestCase 6 | context 'TagLib::FileRef.open' do 7 | should 'return result' do 8 | title = TagLib::FileRef.open('test/data/vorbis.oga', false) do |file| 9 | tag = file.tag 10 | assert_not_nil tag 11 | tag.title 12 | end 13 | assert_equal 'Title', title 14 | end 15 | 16 | should 'close even with exception' do 17 | f = nil 18 | begin 19 | TagLib::FileRef.open('test/data/vorbis.oga', false) do |file| 20 | f = file 21 | raise NotImplementedError 22 | end 23 | flunk('Should have raised NotImplementedError.') 24 | rescue NotImplementedError 25 | begin 26 | f.tag 27 | flunk('Should have raised ObjectPreviouslyDeleted.') 28 | rescue StandardError => e 29 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/fileref_properties_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestFileRefProperties < Test::Unit::TestCase 6 | context 'The crash.mp3 file audio properties' do 7 | setup do 8 | @fileref = TagLib::FileRef.new('test/data/crash.mp3', true, TagLib::AudioProperties::Average) 9 | @properties = @fileref.audio_properties 10 | end 11 | 12 | should 'exist' do 13 | assert_not_nil @properties 14 | end 15 | 16 | should 'contain basic information' do 17 | assert_equal 2, @properties.length_in_seconds 18 | assert_equal 157, @properties.bitrate 19 | assert_equal 44100, @properties.sample_rate 20 | assert_equal 2, @properties.channels 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/fileref_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | require 'fileutils' 6 | 7 | class TestFileRefWrite < Test::Unit::TestCase 8 | SAMPLE_FILE = 'test/data/sample.mp3' 9 | OUTPUT_FILE = 'test/data/output.mp3' 10 | 11 | context 'TagLib::FileRef' do 12 | setup do 13 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 14 | @file = TagLib::MPEG::File.new(OUTPUT_FILE, false) 15 | end 16 | 17 | should 'be able to save the title' do 18 | tag = @file.tag 19 | assert_not_nil tag 20 | tag.title = 'New Title' 21 | success = @file.save 22 | assert success 23 | @file.close 24 | @file = nil 25 | 26 | written_file = TagLib::MPEG::File.new(OUTPUT_FILE, false) 27 | assert_equal 'New Title', written_file.tag.title 28 | written_file.close 29 | end 30 | 31 | should 'not segfault when setting int' do 32 | begin 33 | @file.tag.title = 42 34 | flunk('Should have raised a TypeError') 35 | rescue TypeError 36 | # this is good 37 | end 38 | end 39 | 40 | teardown do 41 | if @file 42 | @file.close 43 | @file = nil 44 | end 45 | FileUtils.rm OUTPUT_FILE 46 | end 47 | end 48 | 49 | context 'TagLib::FileRef.open' do 50 | setup do 51 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 52 | end 53 | 54 | should 'be able to save file' do 55 | TagLib::MPEG::File.open(OUTPUT_FILE, false) do |file| 56 | tag = file.tag 57 | tag.title = 'New Title' 58 | file.save 59 | end 60 | 61 | title = TagLib::MPEG::File.open(OUTPUT_FILE, false) do |file| 62 | tag = file.tag 63 | tag.title 64 | end 65 | assert_equal 'New Title', title 66 | end 67 | 68 | teardown do 69 | FileUtils.rm OUTPUT_FILE 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/flac_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class FlacFileTest < Test::Unit::TestCase 6 | context 'TagLib::FLAC::File' do 7 | setup do 8 | @file = TagLib::FLAC::File.new('test/data/flac.flac') 9 | end 10 | 11 | should 'have a tag' do 12 | tag = @file.tag 13 | assert_not_nil tag 14 | assert_equal TagLib::Tag, tag.class 15 | end 16 | 17 | should 'have XiphComment' do 18 | assert @file.xiph_comment? 19 | tag = @file.xiph_comment 20 | assert_not_nil tag 21 | assert_equal TagLib::Ogg::XiphComment, tag.class 22 | assert_not_nil @file.xiph_comment(true) 23 | end 24 | 25 | should 'have method for ID3v1 tag' do 26 | refute @file.id3v1_tag? 27 | assert_nil @file.id3v1_tag 28 | assert_not_nil @file.id3v1_tag(true) 29 | end 30 | 31 | should 'have method for ID3v2 tag' do 32 | refute @file.id3v2_tag? 33 | assert_nil @file.id3v2_tag 34 | assert_not_nil @file.id3v2_tag(true) 35 | end 36 | 37 | should 'support stripping tags by type' do 38 | @file.strip(TagLib::FLAC::File::XiphComment) 39 | assert @file.xiph_comment? 40 | end 41 | 42 | context 'audio properties' do 43 | setup do 44 | @properties = @file.audio_properties 45 | end 46 | 47 | should 'exist' do 48 | assert_not_nil @properties 49 | end 50 | 51 | should 'contain basic information' do 52 | assert_equal 1, @properties.length_in_seconds 53 | assert_equal 1017, @properties.length_in_milliseconds 54 | assert_equal 209, @properties.bitrate 55 | assert_equal 44100, @properties.sample_rate 56 | assert_equal 1, @properties.channels 57 | end 58 | 59 | should 'contain flac-specific information' do 60 | assert_equal 16, @properties.bits_per_sample 61 | s = ['78d19b86df2cd488b35957e6bd884968'].pack('H*') 62 | assert_equal s, @properties.signature 63 | end 64 | end 65 | 66 | should 'have pictures' do 67 | refute_empty @file.picture_list 68 | end 69 | 70 | context 'first picture' do 71 | setup do 72 | @picture = @file.picture_list.first 73 | end 74 | 75 | should 'be a TagLib::FLAC::Picture,' do 76 | assert_equal TagLib::FLAC::Picture, @picture.class 77 | end 78 | 79 | should 'have meta-data' do 80 | assert_equal TagLib::FLAC::Picture::FrontCover, @picture.type 81 | assert_equal 'image/jpeg', @picture.mime_type 82 | assert_equal 'Globe', @picture.description 83 | assert_equal 90, @picture.width 84 | assert_equal 90, @picture.height 85 | assert_equal 8, @picture.color_depth 86 | assert_equal 0, @picture.num_colors 87 | end 88 | 89 | should 'have data' do 90 | picture_data = File.open('test/data/globe_east_90.jpg', 'rb') { |f| f.read } 91 | assert_equal picture_data, @picture.data 92 | end 93 | end 94 | 95 | should 'support removing a picture' do 96 | refute_empty @file.picture_list 97 | @file.remove_picture(@file.picture_list.first) 98 | assert_empty @file.picture_list 99 | end 100 | 101 | should 'support removing all pictures' do 102 | refute_empty @file.picture_list 103 | @file.remove_pictures 104 | assert_empty @file.picture_list 105 | end 106 | 107 | teardown do 108 | @file.close 109 | @file = nil 110 | end 111 | end 112 | 113 | context 'TagLib::FLAC::File.open' do 114 | should 'should work' do 115 | title = nil 116 | TagLib::FLAC::File.open('test/data/flac.flac', false) do |file| 117 | title = file.tag.title 118 | end 119 | assert_equal 'Title', title 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /test/flac_file_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class FlacFileWriteTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/flac.flac' 7 | OUTPUT_FILE = 'test/data/output.flac' 8 | PICTURE_FILE = 'test/data/globe_east_90.jpg' 9 | 10 | def reloaded(&block) 11 | TagLib::FLAC::File.open(OUTPUT_FILE, false, &block) 12 | end 13 | 14 | context 'TagLib::FLAC::File' do 15 | setup do 16 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 17 | @file = TagLib::FLAC::File.new(OUTPUT_FILE, false) 18 | end 19 | 20 | should 'be able to save the title' do 21 | tag = @file.tag 22 | assert_not_nil tag 23 | tag.title = 'New Title' 24 | success = @file.save 25 | assert success 26 | @file.close 27 | @file = nil 28 | 29 | written_title = reloaded do |file| 30 | file.tag.title 31 | end 32 | assert_equal 'New Title', written_title 33 | end 34 | 35 | should 'be able to remove pictures' do 36 | assert_equal 1, @file.picture_list.size 37 | @file.remove_pictures 38 | assert_equal 0, @file.picture_list.size 39 | success = @file.save 40 | assert success 41 | end 42 | 43 | should 'be able to add and save a new picture' do 44 | picture_data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 45 | 46 | pic = TagLib::FLAC::Picture.new 47 | pic.type = TagLib::FLAC::Picture::FrontCover 48 | pic.mime_type = 'image/jpeg' 49 | pic.description = 'desc' 50 | pic.width = 90 51 | pic.height = 90 52 | pic.data = picture_data 53 | 54 | assert_equal 1, @file.picture_list.size 55 | @file.add_picture(pic) 56 | assert_equal 2, @file.picture_list.size 57 | 58 | success = @file.save 59 | assert success 60 | 61 | reloaded do |file| 62 | written_pic = file.picture_list.last 63 | assert_equal TagLib::FLAC::Picture::FrontCover, written_pic.type 64 | assert_equal 'image/jpeg', written_pic.mime_type 65 | assert_equal 'desc', written_pic.description 66 | assert_equal 90, written_pic.width 67 | assert_equal 90, written_pic.height 68 | assert_equal picture_data, written_pic.data 69 | end 70 | end 71 | 72 | teardown do 73 | if @file 74 | @file.close 75 | @file = nil 76 | end 77 | FileUtils.rm OUTPUT_FILE 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/flac_picture_memory_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestFlacPictureMemory < Test::Unit::TestCase 6 | N = 10000 7 | 8 | context 'TagLib::FLAC::Picture' do 9 | setup do 10 | end 11 | 12 | should 'release memory when closing flac file with picture data' do 13 | c = 0 14 | N.times do 15 | TagLib::FLAC::File.open('test/data/flac.flac', false) do |f| 16 | f.picture_list.each do |_p| 17 | c += 1 18 | end 19 | end 20 | end 21 | assert_equal N, c 22 | end 23 | 24 | should 'process a flac file without picture data' do 25 | c = 0 26 | N.times do 27 | TagLib::FLAC::File.open('test/data/flac_nopic.flac', false) do |f| 28 | f.picture_list.each do |_p| 29 | c += 1 30 | end 31 | end 32 | end 33 | assert_equal 0, c 34 | end 35 | 36 | teardown do 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require 'rubygems' 4 | require 'test/unit' 5 | require 'shoulda-context' 6 | 7 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 8 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 9 | 10 | require 'taglib' 11 | 12 | HAVE_ENCODING = !RUBY_VERSION.start_with?('1.8') 13 | -------------------------------------------------------------------------------- /test/id3v1_genres_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestVorbisTag < Test::Unit::TestCase 6 | context 'TagLib::ID3v1' do 7 | should 'list genres' do 8 | assert_equal 'Jazz', TagLib::ID3v1.genre_list[8] 9 | end 10 | 11 | should 'map genres to their index' do 12 | assert_equal 8, TagLib::ID3v1.genre_map['Jazz'] 13 | end 14 | 15 | should 'support to query the value of a genre' do 16 | assert_equal 'Jazz', TagLib::ID3v1.genre(8) 17 | assert_equal '', TagLib::ID3v1.genre(255) 18 | end 19 | 20 | should 'support to query the index of a genre' do 21 | assert_equal 8, TagLib::ID3v1.genre_index('Jazz') 22 | assert_equal 255, TagLib::ID3v1.genre_index('Unknown') 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/id3v1_tag_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v1Tag < Test::Unit::TestCase 6 | context 'The id3v1.mp3 file' do 7 | setup do 8 | read_properties = false 9 | @file = TagLib::MPEG::File.new('test/data/id3v1.mp3', read_properties) 10 | end 11 | 12 | should 'have an ID3v1 tag' do 13 | assert_not_nil @file.id3v1_tag 14 | end 15 | 16 | context 'ID3v1 tag' do 17 | setup do 18 | @tag = @file.id3v1_tag 19 | end 20 | 21 | should 'have basic properties' do 22 | assert_equal 'Title', @tag.title 23 | assert_equal 'Artist', @tag.artist 24 | assert_equal 'Album', @tag.album 25 | assert_equal 'Comment', @tag.comment 26 | assert_equal 'Pop', @tag.genre 27 | assert_equal 13, @tag.genre_number 28 | assert_equal 2011, @tag.year 29 | assert_equal 7, @tag.track 30 | assert_equal false, @tag.empty? 31 | end 32 | end 33 | 34 | teardown do 35 | @file.close 36 | @file = nil 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/id3v2_frames_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2Frames < Test::Unit::TestCase 6 | context "The sample.mp3 file's frames" do 7 | setup do 8 | read_properties = false 9 | # It's important that file is an instance variable, otherwise the 10 | # tag would get garbage collected along with the file, even if tag 11 | # itself would still be reachable. The reason is because 12 | # TagLib::MPEG::File owns the TagLib::ID3v2::Tag and automatically 13 | # deletes it in its destructor. 14 | @file = TagLib::MPEG::File.new('test/data/sample.mp3', read_properties) 15 | _picture_file = File.open('test/data/globe_east_540.jpg', 'rb') do |f| 16 | @picture_data = f.read 17 | end 18 | @tag = @file.id3v2_tag 19 | @frames = @tag.frame_list 20 | end 21 | 22 | should 'be complete' do 23 | assert_not_nil @frames 24 | assert_equal 11, @frames.size 25 | frame = @frames.first 26 | assert_equal 'Dummy Title', frame.to_string 27 | end 28 | 29 | should 'be enumerable' do 30 | ids = @frames.collect(&:frame_id) 31 | assert_equal %w[TIT2 TPE1 TALB TRCK TDRC 32 | COMM COMM TCON TXXX COMM APIC], ids 33 | end 34 | 35 | should 'be automatically converted' do 36 | apic = @tag.frame_list('APIC').first 37 | comm = @tag.frame_list('COMM').first 38 | tit2 = @tag.frame_list('TIT2').first 39 | txxx = @tag.frame_list('TXXX').first 40 | assert_equal TagLib::ID3v2::AttachedPictureFrame, apic.class 41 | assert_equal TagLib::ID3v2::CommentsFrame, comm.class 42 | assert_equal TagLib::ID3v2::TextIdentificationFrame, tit2.class 43 | assert_equal TagLib::ID3v2::UserTextIdentificationFrame, txxx.class 44 | end 45 | 46 | should 'not fail for nil String' do 47 | assert_equal [], @tag.frame_list(nil) 48 | end 49 | 50 | should 'be removable' do 51 | assert_equal 11, @tag.frame_list.size 52 | tit2 = @tag.frame_list('TIT2').first 53 | @tag.remove_frame(tit2) 54 | assert_equal 10, @tag.frame_list.size 55 | begin 56 | tit2.to_string 57 | flunk('Should have raised ObjectPreviouslyDeleted.') 58 | rescue StandardError => e 59 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 60 | end 61 | end 62 | 63 | should 'be removable by ID' do 64 | frames = @tag.frame_list 65 | @tag.remove_frames('COMM') 66 | tit2 = frames.find { |f| f.frame_id == 'TIT2' } 67 | # Other frames should still be accessible 68 | assert_equal 'Dummy Title', tit2.to_s 69 | end 70 | 71 | context 'APIC frame' do 72 | setup do 73 | @apic = @tag.frame_list('APIC').first 74 | end 75 | 76 | should 'have a type' do 77 | assert_equal TagLib::ID3v2::AttachedPictureFrame::FrontCover, @apic.type 78 | end 79 | 80 | should 'have a description' do 81 | assert_equal 'Blue Marble', @apic.description 82 | end 83 | 84 | should 'have a mime type' do 85 | assert_equal 'image/jpeg', @apic.mime_type 86 | end 87 | 88 | should 'have picture bytes' do 89 | assert_equal 61_649, @apic.picture.size 90 | assert_equal @picture_data.encoding, @apic.picture.encoding if HAVE_ENCODING 91 | assert_equal @picture_data, @apic.picture 92 | end 93 | end 94 | 95 | context 'CTOC and CHAP frames' do 96 | setup do 97 | @chapters = [ 98 | { id: 'CH1', start_time: 100, end_time: 200 }, 99 | { id: 'CH2', start_time: 201, end_time: 300 }, 100 | { id: 'CH3', start_time: 301, end_time: 400 } 101 | ] 102 | 103 | @default_ctoc = TagLib::ID3v2::TableOfContentsFrame.new('Test') 104 | @default_chap = TagLib::ID3v2::ChapterFrame.new('Test', 0, 1, 0xFFFFFFFF, 0xFFFFFFFF) 105 | end 106 | 107 | should 'not have a CTOC frame' do 108 | assert_equal [], @tag.frame_list('CTOC') 109 | end 110 | 111 | should 'not have a CHAP frame' do 112 | assert_equal [], @tag.frame_list('CHAP') 113 | end 114 | 115 | should 'have one CTOC frame (only one Table of Contents)' do 116 | toc = TagLib::ID3v2::TableOfContentsFrame.new('TOC') 117 | toc.is_top_level = true 118 | toc.is_ordered = true 119 | 120 | @chapters.each do |chapter| 121 | toc.add_child_element(chapter[:id]) 122 | end 123 | 124 | @tag.add_frame(toc) 125 | 126 | ctoc_frame_list = @tag.frame_list('CTOC') 127 | assert_equal @default_ctoc.class, ctoc_frame_list.first.class 128 | assert_equal 1, ctoc_frame_list.size 129 | assert_equal 'TOC', ctoc_frame_list.first.element_id 130 | assert_equal 3, ctoc_frame_list.first.child_elements.size 131 | assert_equal %w[CH1 CH2 CH3], ctoc_frame_list.first.child_elements 132 | end 133 | 134 | should 'have CHAP frames (multiple chapters)' do 135 | start_offset = 0xFFFFFFFF 136 | end_offset = 0xFFFFFFFF 137 | 138 | @chapters.each do |chapter| 139 | chapter_frame = TagLib::ID3v2::ChapterFrame.new( 140 | chapter[:id], 141 | chapter[:start_time].to_i, 142 | chapter[:end_time].to_i, 143 | start_offset, 144 | end_offset 145 | ) 146 | 147 | @tag.add_frame(chapter_frame) 148 | end 149 | 150 | chap_frame_list = @tag.frame_list('CHAP') 151 | assert_equal @default_chap.class, chap_frame_list.first.class 152 | assert_equal 3, chap_frame_list.size 153 | assert_equal 'CH1', chap_frame_list[0].element_id 154 | assert_equal 'CH2', chap_frame_list[1].element_id 155 | assert_equal 'CH3', chap_frame_list[2].element_id 156 | end 157 | end 158 | 159 | context 'TXXX frame' do 160 | setup do 161 | @txxx_frame = @tag.frame_list('TXXX').first 162 | end 163 | 164 | should 'exist' do 165 | assert_not_nil @txxx_frame 166 | end 167 | 168 | should 'have to_s' do 169 | expected = /\[MusicBrainz Album Id\].* 992dc19a-5631-40f5-b252-fbfedbc328a9/ 170 | assert_match expected, @txxx_frame.to_string 171 | end 172 | 173 | should 'have field_list' do 174 | assert_equal ['MusicBrainz Album Id', '992dc19a-5631-40f5-b252-fbfedbc328a9'], @txxx_frame.field_list 175 | end 176 | 177 | should 'constructor with all arguments should work' do 178 | frame = TagLib::ID3v2::UserTextIdentificationFrame.new("UserFrame", ["Some User Text"], TagLib::String::UTF8) 179 | assert_equal ["UserFrame", "Some User Text"], frame.field_list 180 | end 181 | end 182 | 183 | teardown do 184 | @file.close 185 | @file = nil 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /test/id3v2_header_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2Header < Test::Unit::TestCase 6 | context 'The sample.mp3 file' do 7 | setup do 8 | read_properties = false 9 | @file = TagLib::MPEG::File.new('test/data/sample.mp3', read_properties) 10 | @tag = @file.id3v2_tag 11 | end 12 | 13 | should 'have a ID3v2 header' do 14 | assert_not_nil @tag.header 15 | end 16 | 17 | context 'header' do 18 | setup do 19 | @header = @tag.header 20 | end 21 | 22 | should 'have a major version' do 23 | assert_equal 3, @header.major_version 24 | end 25 | 26 | should 'have a revision number' do 27 | assert_equal 0, @header.revision_number 28 | end 29 | 30 | should 'have a tag size' do 31 | assert_equal 63_478, @header.tag_size 32 | end 33 | 34 | should 'not have a footer' do 35 | assert_equal false, @header.footer_present 36 | end 37 | 38 | should 'not have an extended header' do 39 | assert_equal false, @header.extended_header 40 | end 41 | 42 | context 'changing the major version' do 43 | setup do 44 | @header.major_version = 4 45 | end 46 | 47 | should 'have a different major verson' do 48 | assert_equal 4, @header.major_version 49 | end 50 | 51 | teardown do 52 | @header.major_version = 3 53 | end 54 | end 55 | end 56 | 57 | teardown do 58 | @file.close 59 | @file = nil 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/id3v2_memory_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2Memory < Test::Unit::TestCase 6 | N = 1000 7 | 8 | context 'TagLib::ID3v2' do 9 | setup do 10 | @file = TagLib::MPEG::File.new('test/data/sample.mp3', false) 11 | @tag = @file.id3v2_tag 12 | @apic = @tag.frame_list('APIC').first 13 | end 14 | 15 | should 'not corrupt memory with FrameList' do 16 | N.times do 17 | @tag.frame_list 18 | end 19 | end 20 | 21 | should 'not corrupt memory with ByteVector' do 22 | data = nil 23 | N.times do 24 | data = @apic.picture 25 | end 26 | N.times do 27 | @apic.picture = data 28 | end 29 | end 30 | 31 | should 'not corrupt memory with StringList' do 32 | txxx = @tag.frame_list('TXXX').first 33 | N.times do 34 | txxx.field_list 35 | end 36 | N.times do 37 | txxx.field_list = %w[one two three] 38 | end 39 | end 40 | 41 | should 'not segfault when tag is deleted along with file' do 42 | @file = nil 43 | begin 44 | N.times do 45 | GC.start 46 | @tag.title 47 | end 48 | rescue StandardError => e 49 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 50 | else 51 | raise 'GC did not delete file, unsure if test was successful.' 52 | end 53 | end 54 | 55 | should 'not segfault when audio properties are deleted along with file' do 56 | file = TagLib::MPEG::File.new('test/data/crash.mp3', true) 57 | properties = file.audio_properties 58 | file.close 59 | begin 60 | N.times do 61 | GC.start 62 | properties.bitrate 63 | end 64 | rescue StandardError => e 65 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 66 | else 67 | raise 'GC did not delete file, unsure if test was successful.' 68 | end 69 | end 70 | 71 | should 'not throw when adding frame via Tag.add_frame' do 72 | tcom = TagLib::ID3v2::TextIdentificationFrame.new('TCOM', TagLib::String::Latin1) 73 | tcom.text = 'Some composer' 74 | @tag.add_frame tcom 75 | # the following leads to an ObjectPreviouslyDeleted error (see Issue #8) 76 | assert_nothing_raised do 77 | @tag.frame_list.find { |fr| fr.frame_id == 'TCOM' } 78 | end 79 | end 80 | 81 | teardown do 82 | if @file 83 | @file.close 84 | @file = nil 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/id3v2_relative_volume_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2RelativeVolumeFrame < Test::Unit::TestCase 6 | context 'The relative-volume.mp3 RVA2 frame' do 7 | setup do 8 | @file = TagLib::MPEG::File.new('test/data/relative-volume.mp3') 9 | @tag = @file.id3v2_tag 10 | @rv = @tag.frame_list('RVA2').first 11 | end 12 | 13 | should 'exist' do 14 | assert_not_nil @rv 15 | end 16 | 17 | should 'have channels' do 18 | expected = [TagLib::ID3v2::RelativeVolumeFrame::MasterVolume, TagLib::ID3v2::RelativeVolumeFrame::Subwoofer] 19 | assert_equal expected, @rv.channels 20 | end 21 | 22 | should 'have volume adjustments' do 23 | assert_equal 512, @rv.volume_adjustment_index 24 | assert_equal 1.0, @rv.volume_adjustment 25 | assert_equal 1024, @rv.volume_adjustment_index(TagLib::ID3v2::RelativeVolumeFrame::Subwoofer) 26 | assert_equal 2.0, @rv.volume_adjustment(TagLib::ID3v2::RelativeVolumeFrame::Subwoofer) 27 | end 28 | 29 | should 'have peak volumes' do 30 | master_pv = @rv.peak_volume 31 | assert_equal 8, master_pv.bits_representing_peak 32 | assert_equal 0b01000001.chr, master_pv.peak_volume 33 | 34 | subwoofer_pv = @rv.peak_volume(TagLib::ID3v2::RelativeVolumeFrame::Subwoofer) 35 | assert_equal 4, subwoofer_pv.bits_representing_peak 36 | assert_equal 0b00111111.chr, subwoofer_pv.peak_volume 37 | end 38 | 39 | should 'accept new volume adjustments' do 40 | @rv.set_volume_adjustment_index(2048, TagLib::ID3v2::RelativeVolumeFrame::FrontCentre) 41 | assert_equal 2048, @rv.volume_adjustment_index(TagLib::ID3v2::RelativeVolumeFrame::FrontCentre) 42 | @rv.set_volume_adjustment(4.0, TagLib::ID3v2::RelativeVolumeFrame::FrontLeft) 43 | assert_equal 4.0, @rv.volume_adjustment(TagLib::ID3v2::RelativeVolumeFrame::FrontLeft) 44 | end 45 | 46 | should 'accept new peak volumes' do 47 | pv = TagLib::ID3v2::PeakVolume.new 48 | assert_equal 0, pv.bits_representing_peak 49 | assert_equal '', pv.peak_volume 50 | pv.bits_representing_peak = 6 51 | pv.peak_volume = 0b110111.chr 52 | @rv.set_peak_volume(pv, TagLib::ID3v2::RelativeVolumeFrame::BackLeft) 53 | 54 | pv2 = @rv.peak_volume(TagLib::ID3v2::RelativeVolumeFrame::BackLeft) 55 | assert_equal 6, pv2.bits_representing_peak 56 | assert_equal 0b110111.chr, pv2.peak_volume 57 | end 58 | 59 | teardown do 60 | @file.close 61 | @file = nil 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/id3v2_tag_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2Tag < Test::Unit::TestCase 6 | context 'The sample.mp3 file' do 7 | setup do 8 | read_properties = false 9 | @file = TagLib::MPEG::File.new('test/data/sample.mp3', read_properties) 10 | end 11 | 12 | should 'have an ID3v2 tag' do 13 | assert_not_nil @file.id3v2_tag 14 | end 15 | 16 | context 'tag' do 17 | setup do 18 | @tag = @file.id3v2_tag 19 | end 20 | 21 | should 'have basic properties' do 22 | assert_equal 'Dummy Title', @tag.title 23 | assert_equal 'Dummy Artist', @tag.artist 24 | assert_equal 'Dummy Album', @tag.album 25 | assert_equal 'Dummy Comment', @tag.comment 26 | assert_equal 'Pop', @tag.genre 27 | assert_equal 2000, @tag.year 28 | assert_equal 1, @tag.track 29 | assert_equal false, @tag.empty? 30 | end 31 | end 32 | 33 | teardown do 34 | @file.close 35 | @file = nil 36 | end 37 | end 38 | 39 | context 'A new ID3v2::Tag' do 40 | setup do 41 | @tag = TagLib::ID3v2::Tag.new 42 | end 43 | 44 | should 'be empty' do 45 | assert @tag.empty? 46 | end 47 | 48 | should 'have empty string attributes' do 49 | assert_equal '', @tag.title 50 | assert_equal '', @tag.artist 51 | assert_equal '', @tag.album 52 | assert_equal '', @tag.comment 53 | assert_equal '', @tag.genre 54 | end 55 | 56 | should 'have 0 for numeric attributes' do 57 | assert_equal 0, @tag.track 58 | assert_equal 0, @tag.year 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/id3v2_unicode_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2Unicode < Test::Unit::TestCase 6 | context 'The unicode.mp3 file' do 7 | setup do 8 | read_properties = false 9 | @file = TagLib::MPEG::File.new('test/data/unicode.mp3', read_properties) 10 | end 11 | 12 | should 'have an ID3v2 tag' do 13 | assert_not_nil @file.id3v2_tag 14 | end 15 | 16 | if HAVE_ENCODING 17 | context 'tag' do 18 | setup do 19 | @tag = @file.id3v2_tag 20 | end 21 | 22 | should 'return strings in the right encoding' do 23 | assert_equal 'UTF-8', @tag.title.encoding.to_s 24 | end 25 | 26 | should 'convert strings to the right encoding' do 27 | # Unicode Snowman in UTF-16 28 | utf16_encoded = "\x26\x03".dup # mutable 29 | utf16_encoded.force_encoding('UTF-16BE') 30 | 31 | # It should be converted here 32 | @tag.title = utf16_encoded 33 | 34 | result = @tag.title 35 | 36 | # In order for == to work, they have to be in the same 37 | # encoding 38 | utf8_encoded = utf16_encoded.encode('UTF-8') 39 | assert_equal utf8_encoded, result 40 | end 41 | end 42 | end 43 | 44 | teardown do 45 | @file.close 46 | @file = nil 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/id3v2_unknown_frames_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestID3v2UnknownFrames < Test::Unit::TestCase 6 | context 'UnknownFrame' do 7 | setup do 8 | read_properties = false 9 | @file = TagLib::MPEG::File.new('test/data/sample.mp3', read_properties) 10 | @tag = @file.id3v2_tag 11 | end 12 | 13 | should 'should be returned with correct class' do 14 | f = TagLib::ID3v2::UnknownFrame.new('TDAT') 15 | assert_not_nil f 16 | @tag.add_frame(f) 17 | frames = @tag.frame_list('TDAT') 18 | tdat = frames.first 19 | assert_not_nil tdat 20 | # By looking at ID alone, it would have returned a 21 | # TextIdentificationFrame. So make sure the correct 22 | # class is returned here, because it would result in 23 | # segfaults when calling methods on it. 24 | assert_equal TagLib::ID3v2::UnknownFrame, tdat.class 25 | end 26 | 27 | teardown do 28 | @file.close 29 | @file = nil 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/id3v2_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | require 'fileutils' 6 | 7 | class TestID3v2Write < Test::Unit::TestCase 8 | SAMPLE_FILE = 'test/data/sample.mp3' 9 | OUTPUT_FILE = 'test/data/output.mp3' 10 | PICTURE_FILE = 'test/data/globe_east_540.jpg' 11 | 12 | def reloaded(&block) 13 | TagLib::MPEG::File.open(OUTPUT_FILE, false, &block) 14 | end 15 | 16 | context 'TagLib::MPEG::File' do 17 | setup do 18 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 19 | @file = TagLib::MPEG::File.new(OUTPUT_FILE, false) 20 | end 21 | 22 | should 'be able to strip the tag' do 23 | assert_not_nil @file.id3v2_tag 24 | success = @file.strip 25 | assert success 26 | assert_nil @file.id3v2_tag 27 | @file.close 28 | @file = nil 29 | 30 | reloaded do |file| 31 | assert_equal true, file.id3v1_tag.empty? 32 | assert_equal true, file.id3v2_tag.empty? 33 | end 34 | end 35 | 36 | should 'be able to save only ID3v2 tag' do 37 | assert_not_nil @file.id3v2_tag 38 | assert_not_nil @file.id3v1_tag 39 | @file.save(TagLib::MPEG::File::ID3v2) 40 | @file.close 41 | @file = nil 42 | 43 | reloaded do |file| 44 | id3v1_tag = file.id3v1_tag 45 | id3v2_tag = file.id3v2_tag 46 | 47 | # TagLib always creates the tags 48 | assert_not_nil id3v1_tag 49 | assert_not_nil id3v2_tag 50 | 51 | assert_equal true, file.id3v1_tag.empty? 52 | assert_equal false, file.id3v2_tag.empty? 53 | end 54 | end 55 | 56 | should 'be able to save ID3v2.3' do 57 | success = @file.save(TagLib::MPEG::File::ID3v2, TagLib::File::StripOthers, TagLib::ID3v2::V3) 58 | assert_equal true, success 59 | @file.close 60 | @file = nil 61 | 62 | header = File.open(OUTPUT_FILE, 'rb') do |f| 63 | f.read(5) 64 | end 65 | # 3 stands for v2.3 66 | s = "ID3#{3.chr}#{0.chr}" 67 | assert_equal s, header 68 | end 69 | 70 | should 'be able to set fields to nil' do 71 | tag = @file.id3v2_tag 72 | tag.title = nil 73 | assert_equal [], tag.frame_list('TIT2') 74 | end 75 | 76 | context 'with a fresh tag' do 77 | setup do 78 | @file.strip 79 | @tag = @file.id3v2_tag(true) 80 | end 81 | 82 | should 'be able to create a new tag' do 83 | assert_not_nil @tag 84 | assert_equal 0, @tag.frame_list.size 85 | end 86 | 87 | should 'be able to save it' do 88 | success = @file.save 89 | assert success 90 | end 91 | 92 | should 'be able to add a new frame to it and read it back' do 93 | picture_data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 94 | 95 | apic = TagLib::ID3v2::AttachedPictureFrame.new 96 | apic.mime_type = 'image/jpeg' 97 | apic.description = 'desc' 98 | apic.text_encoding = TagLib::String::UTF8 99 | apic.picture = picture_data 100 | apic.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover 101 | 102 | @tag.add_frame(apic) 103 | 104 | success = @file.save 105 | assert success 106 | @file.close 107 | @file = nil 108 | 109 | written_file = TagLib::MPEG::File.new(OUTPUT_FILE, false) 110 | written_apic = written_file.id3v2_tag.frame_list('APIC').first 111 | assert_equal 'image/jpeg', written_apic.mime_type 112 | assert_equal 'desc', written_apic.description 113 | assert_equal picture_data, written_apic.picture 114 | written_file.close 115 | end 116 | 117 | should 'be able to set field_list' do 118 | tit2 = TagLib::ID3v2::TextIdentificationFrame.new('TIT2', TagLib::String::UTF8) 119 | texts = %w[one two] 120 | tit2.field_list = texts 121 | assert_equal texts, tit2.field_list 122 | @tag.add_frame(tit2) 123 | success = @file.save 124 | assert success 125 | end 126 | 127 | should 'not fail when field_list is nil' do 128 | tit2 = TagLib::ID3v2::TextIdentificationFrame.new('TIT2', TagLib::String::UTF8) 129 | tit2.field_list = nil 130 | assert_equal [], tit2.field_list 131 | end 132 | 133 | if HAVE_ENCODING 134 | should 'be able to set unicode fields' do 135 | # Hello, Unicode Snowman (not in Latin1) 136 | text = "Hello, \u{2603}" 137 | 138 | # If we don't set the default text encoding to UTF-8, taglib 139 | # will print a warning 140 | frame_factory = TagLib::ID3v2::FrameFactory.instance 141 | frame_factory.default_text_encoding = TagLib::String::UTF8 142 | 143 | @tag.title = text 144 | @file.save 145 | 146 | assert_equal text, @tag.title 147 | end 148 | end 149 | end 150 | 151 | teardown do 152 | if @file 153 | @file.close 154 | @file = nil 155 | end 156 | FileUtils.rm OUTPUT_FILE 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /test/mp4_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class MP4FileTest < Test::Unit::TestCase 6 | context 'TagLib::MP4::File' do 7 | setup do 8 | @file = TagLib::MP4::File.new('test/data/mp4.m4a') 9 | @tag = @file.tag 10 | end 11 | 12 | should 'have an MP4 tag' do 13 | assert @file.mp4_tag? 14 | refute_nil @tag 15 | end 16 | 17 | should 'contain basic tag information' do 18 | assert_equal 'Title', @tag.title 19 | assert_equal 'Artist', @tag.artist 20 | assert_equal 'Album', @tag.album 21 | assert_equal 'Comment', @tag.comment 22 | assert_equal 'Pop', @tag.genre 23 | assert_equal 2011, @tag.year 24 | assert_equal 7, @tag.track 25 | 26 | assert_equal false, @tag.empty? 27 | end 28 | 29 | should 'support testing for the presence of items' do 30 | refute @tag.contains 'unknown' 31 | assert @tag.contains 'trkn' 32 | end 33 | 34 | should 'support accessing items' do 35 | refute @tag['unkn'].valid? 36 | 37 | assert @tag['trkn'].valid? 38 | assert_equal 7, @tag.track 39 | end 40 | 41 | should 'support editing items' do 42 | @tag['trkn'] = TagLib::MP4::Item.from_int(1) 43 | assert_equal 1, @tag.track 44 | end 45 | 46 | should 'support removing items' do 47 | assert @tag.contains 'trkn' 48 | @tag.remove_item('trkn') 49 | refute @tag.contains 'trkn' 50 | end 51 | 52 | context 'audio properties' do 53 | setup do 54 | @properties = @file.audio_properties 55 | end 56 | 57 | should 'exist' do 58 | assert_not_nil @properties 59 | end 60 | 61 | should 'contain basic information' do 62 | assert_equal 1, @properties.length_in_seconds 63 | assert_equal 55, @properties.bitrate 64 | assert_equal 44100, @properties.sample_rate 65 | # The test file is mono, this appears to be a TagLib bug 66 | assert_equal 2, @properties.channels 67 | end 68 | 69 | should 'contain mp4-specific information' do 70 | assert_equal 16, @properties.bits_per_sample 71 | assert_equal false, @properties.encrypted? 72 | assert_equal TagLib::MP4::Properties::AAC, @properties.codec 73 | end 74 | end 75 | 76 | teardown do 77 | @file.close 78 | @file = nil 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/mp4_file_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class MP4FileWriteTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/mp4.m4a' 7 | OUTPUT_FILE = 'test/data/output.m4a' 8 | PICTURE_FILE = 'test/data/globe_east_540.jpg' 9 | 10 | def reloaded(&block) 11 | TagLib::MP4::File.open(OUTPUT_FILE, false, &block) 12 | end 13 | 14 | context 'TagLib::MP4::File' do 15 | setup do 16 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 17 | @file = TagLib::MP4::File.new(OUTPUT_FILE, false) 18 | end 19 | 20 | should 'be able to save the title' do 21 | tag = @file.tag 22 | assert_not_nil tag 23 | tag.title = 'New Title' 24 | success = @file.save 25 | assert success 26 | @file.close 27 | @file = nil 28 | 29 | written_title = reloaded do |file| 30 | file.tag.title 31 | end 32 | assert_equal 'New Title', written_title 33 | end 34 | 35 | should 'be able to add and save new cover art' do 36 | item_map = @file.tag.item_map 37 | cover_art_list = item_map['covr'].to_cover_art_list 38 | assert_equal 1, cover_art_list.size 39 | 40 | data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 41 | new_cover_art = TagLib::MP4::CoverArt.new(TagLib::MP4::CoverArt::JPEG, data) 42 | 43 | cover_art_list << new_cover_art 44 | item_map.insert('covr', TagLib::MP4::Item.from_cover_art_list(cover_art_list)) 45 | assert_equal 2, item_map['covr'].to_cover_art_list.size 46 | 47 | success = @file.save 48 | assert success 49 | 50 | reloaded do |file| 51 | written_cover_art = file.tag.item_map['covr'].to_cover_art_list.last 52 | assert_equal TagLib::MP4::CoverArt::JPEG, written_cover_art.format 53 | assert_equal data, written_cover_art.data 54 | end 55 | end 56 | 57 | teardown do 58 | if @file 59 | @file.close 60 | @file = nil 61 | end 62 | FileUtils.rm OUTPUT_FILE 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/mp4_items_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | require 'set' 5 | 6 | class MP4ItemsTest < Test::Unit::TestCase 7 | ITUNES_LEADER = "\xC2\xA9" 8 | 9 | context "The mp4.m4a file's items" do 10 | setup do 11 | @file = TagLib::MP4::File.new('test/data/mp4.m4a') 12 | @tag = @file.tag 13 | @item_map = @file.tag.item_map 14 | @item_keys = [ 15 | 'covr', "#{ITUNES_LEADER}nam", "#{ITUNES_LEADER}ART", "#{ITUNES_LEADER}alb", 16 | "#{ITUNES_LEADER}cmt", "#{ITUNES_LEADER}gen", "#{ITUNES_LEADER}day", 17 | 'trkn', "#{ITUNES_LEADER}too", "#{ITUNES_LEADER}cpy" 18 | ] 19 | end 20 | 21 | context 'item_map' do 22 | should 'exist' do 23 | assert_not_nil @item_map 24 | end 25 | 26 | should 'not be empty' do 27 | assert_equal false, @item_map.empty? 28 | end 29 | 30 | should 'contain 10 items' do 31 | assert_equal @item_keys.count, @item_map.size 32 | end 33 | 34 | should 'have keys' do 35 | assert_equal true, @item_map.contains('trkn') 36 | assert_equal true, @item_map.has_key?("#{ITUNES_LEADER}too") 37 | assert_equal true, @item_map.include?("#{ITUNES_LEADER}cpy") 38 | assert_equal false, @item_map.include?('none such key') 39 | end 40 | 41 | should 'look up keys' do 42 | assert_nil @item_map['none such key'] 43 | assert_equal ['Title'], @item_map["#{ITUNES_LEADER}nam"].to_string_list 44 | end 45 | 46 | should 'be clearable' do 47 | assert_equal 10, @item_map.size 48 | comment = @item_map["#{ITUNES_LEADER}cmt"] 49 | @item_map.clear 50 | assert_equal true, @item_map.empty? 51 | begin 52 | comment.to_string_list 53 | flunk('Should have raised ObjectPreviouslyDeleted.') 54 | rescue StandardError => e 55 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 56 | end 57 | end 58 | 59 | should 'be convertible to an array' do 60 | array = @item_map.to_a 61 | assert_equal 10, array.count 62 | array.each do |object| 63 | assert_equal Array, object.class 64 | assert_equal 2, object.count 65 | assert_equal String, object.first.class 66 | assert_equal TagLib::MP4::Item, object.last.class 67 | assert_includes @item_keys, object.first 68 | end 69 | end 70 | 71 | should 'be convertible to a hash' do 72 | hsh = @item_map.to_h 73 | assert_equal Set.new(hsh.keys), Set.new(@item_keys) 74 | end 75 | end 76 | 77 | should 'be removable' do 78 | assert_equal 10, @item_map.size 79 | title = @item_map["#{ITUNES_LEADER}nam"] 80 | @item_map.erase("#{ITUNES_LEADER}nam") 81 | assert_equal 9, @item_map.size 82 | begin 83 | title.to_string_list 84 | flunk('Should have raised ObjectPreviouslyDeleted.') 85 | rescue StandardError => e 86 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 87 | end 88 | end 89 | 90 | should 'reflect edition of items from the tag' do 91 | assert @tag.contains 'trkn' 92 | track = @item_map['trkn'] 93 | 94 | @tag['trkn'] = TagLib::MP4::Item.from_int(1) 95 | 96 | begin 97 | track.to_int_pair[0] 98 | flunk('Should have raised ObjectPreviouslyDeleted.') 99 | rescue StandardError => e 100 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 101 | end 102 | end 103 | 104 | should 'reflect removal of items from the tag' do 105 | assert @tag.contains 'trkn' 106 | track = @item_map['trkn'] 107 | 108 | @tag.remove_item('trkn') 109 | refute @item_map.contains 'trkn' 110 | 111 | begin 112 | track.to_int_pair[0] 113 | flunk('Should have raised ObjectPreviouslyDeleted.') 114 | rescue StandardError => e 115 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 116 | end 117 | end 118 | 119 | context 'inserting items' do 120 | should 'insert a new item' do 121 | new_title = TagLib::MP4::Item.from_string_list(['new title']) 122 | @item_map.insert("#{ITUNES_LEADER}nam", new_title) 123 | GC.start 124 | assert_equal ['new title'], @item_map["#{ITUNES_LEADER}nam"].to_string_list 125 | end 126 | 127 | should 'unlink items that get replaced' do 128 | title = @item_map["#{ITUNES_LEADER}nam"] 129 | @item_map.insert("#{ITUNES_LEADER}nam", TagLib::MP4::Item.from_int(1)) 130 | begin 131 | title.to_string_list 132 | flunk('Should have raised ObjectPreviouslyDeleted.') 133 | rescue StandardError => e 134 | assert_equal 'ObjectPreviouslyDeleted', e.class.to_s 135 | end 136 | end 137 | end 138 | 139 | context 'TagLib::MP4::Item' do 140 | should 'be creatable from a boolean' do 141 | item = TagLib::MP4::Item.from_bool(false) 142 | assert_equal TagLib::MP4::Item, item.class 143 | assert_equal false, item.to_bool 144 | end 145 | 146 | should 'be creatable from a byte' do 147 | item = TagLib::MP4::Item.from_byte(123) 148 | assert_equal TagLib::MP4::Item, item.class 149 | assert_equal 123, item.to_byte 150 | end 151 | 152 | should 'be creatable from an unsigned int' do 153 | item = TagLib::MP4::Item.from_int(12_346) 154 | assert_equal TagLib::MP4::Item, item.class 155 | assert_equal 12_346, item.to_uint 156 | end 157 | 158 | should 'be creatable from an int' do 159 | item = TagLib::MP4::Item.from_int(-42) 160 | assert_equal TagLib::MP4::Item, item.class 161 | assert_equal(-42, item.to_int) 162 | end 163 | 164 | should 'be creatable from a long long' do 165 | item = TagLib::MP4::Item.from_long_long(1_234_567_890) 166 | assert_equal TagLib::MP4::Item, item.class 167 | assert_equal 1_234_567_890, item.to_long_long 168 | end 169 | 170 | context '.from_int_pair' do 171 | should 'be creatable from a pair of ints' do 172 | item = TagLib::MP4::Item.from_int_pair([123, 456]) 173 | assert_equal TagLib::MP4::Item, item.class 174 | assert_equal [123, 456], item.to_int_pair 175 | end 176 | 177 | should 'raise an error when passed something other than an Array' do 178 | begin 179 | TagLib::MP4::Item.from_int_pair(1) 180 | flunk('Should have raised ArgumentError.') 181 | rescue StandardError => e 182 | assert_equal 'ArgumentError', e.class.to_s 183 | end 184 | end 185 | 186 | should 'raise an error when passed an Array with more than two elements' do 187 | begin 188 | TagLib::MP4::Item.from_int_pair([1, 2, 3]) 189 | flunk('Should have raised ArgumentError.') 190 | rescue StandardError => e 191 | assert_equal 'ArgumentError', e.class.to_s 192 | end 193 | end 194 | 195 | should 'raise an error when passed an Array with less than two elements' do 196 | begin 197 | TagLib::MP4::Item.from_int_pair([1]) 198 | flunk('Should have raised ArgumentError.') 199 | rescue StandardError => e 200 | assert_equal 'ArgumentError', e.class.to_s 201 | end 202 | end 203 | end 204 | 205 | context 'created from an array of strings' do 206 | should 'interpreted as strings with an encoding' do 207 | item = TagLib::MP4::Item.from_string_list(['héllo']) 208 | assert_equal TagLib::MP4::Item, item.class 209 | assert_equal ['héllo'], item.to_string_list 210 | end 211 | end 212 | 213 | should 'be creatable from a CoverArt list' do 214 | cover_art = TagLib::MP4::CoverArt.new(TagLib::MP4::CoverArt::JPEG, 'foo') 215 | item = TagLib::MP4::Item.from_cover_art_list([cover_art]) 216 | assert_equal TagLib::MP4::Item, item.class 217 | new_cover_art = item.to_cover_art_list.first 218 | assert_equal 'foo', new_cover_art.data 219 | assert_equal TagLib::MP4::CoverArt::JPEG, new_cover_art.format 220 | end 221 | end 222 | 223 | context 'TagLib::MP4::CoverArt' do 224 | should 'be creatable from a string' do 225 | cover_art = TagLib::MP4::CoverArt.new(TagLib::MP4::CoverArt::JPEG, 'foo') 226 | assert_equal TagLib::MP4::CoverArt::JPEG, cover_art.format 227 | assert_equal 'foo', cover_art.data 228 | end 229 | end 230 | 231 | teardown do 232 | @file.close 233 | @file = nil 234 | end 235 | end 236 | end 237 | -------------------------------------------------------------------------------- /test/mpeg_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestMPEGFile < Test::Unit::TestCase 6 | context 'The crash.mp3 file' do 7 | setup do 8 | read_properties = true 9 | @file = TagLib::MPEG::File.new('test/data/crash.mp3', read_properties) 10 | end 11 | 12 | should 'have a basic tag' do 13 | tag = @file.tag 14 | assert_not_nil tag 15 | assert_equal TagLib::Tag, tag.class 16 | assert tag.empty? 17 | end 18 | 19 | context 'audio properties' do 20 | setup do 21 | @properties = @file.audio_properties 22 | end 23 | 24 | should 'be MPEG audio properties' do 25 | assert_equal TagLib::MPEG::Properties, @properties.class 26 | end 27 | 28 | should 'contain information' do 29 | assert_equal 2, @properties.length_in_seconds 30 | assert_equal 2299, @properties.length_in_milliseconds 31 | assert_equal 157, @properties.bitrate 32 | assert_equal 44100, @properties.sample_rate 33 | assert_equal 2, @properties.channels 34 | assert_equal TagLib::MPEG::Header::Version1, @properties.version 35 | assert_equal 3, @properties.layer 36 | assert_equal false, @properties.protection_enabled 37 | assert_equal TagLib::MPEG::Header::JointStereo, @properties.channel_mode 38 | assert_equal false, @properties.copyrighted? 39 | assert_equal true, @properties.original? 40 | end 41 | 42 | context 'Xing header' do 43 | setup do 44 | @xing_header = @properties.xing_header 45 | end 46 | 47 | should 'exist' do 48 | assert_not_nil @xing_header 49 | end 50 | 51 | should 'contain information' do 52 | assert @xing_header.valid? 53 | assert_equal 88, @xing_header.total_frames 54 | assert_equal 45_140, @xing_header.total_size 55 | assert_equal TagLib::MPEG::XingHeader::Xing, @xing_header.type 56 | end 57 | end 58 | end 59 | 60 | should 'have no tag' do 61 | refute @file.id3v1_tag? 62 | refute @file.id3v2_tag? 63 | refute @file.ape_tag? 64 | end 65 | 66 | teardown do 67 | @file.close 68 | @file = nil 69 | end 70 | end 71 | 72 | context 'The id3v1.mp3 file' do 73 | setup do 74 | read_properties = true 75 | @file = TagLib::MPEG::File.new('test/data/id3v1.mp3', read_properties) 76 | end 77 | 78 | should 'have a basic tag' do 79 | tag = @file.tag 80 | assert_not_nil tag 81 | assert_equal TagLib::Tag, tag.class 82 | refute tag.empty? 83 | end 84 | 85 | context 'audio properties' do 86 | setup do 87 | @properties = @file.audio_properties 88 | end 89 | 90 | should 'be MPEG audio properties' do 91 | assert_equal TagLib::MPEG::Properties, @properties.class 92 | end 93 | 94 | should 'contain information' do 95 | assert_equal 0, @properties.length_in_seconds 96 | assert_equal 261, @properties.length_in_milliseconds 97 | assert_equal 141, @properties.bitrate 98 | assert_equal 44100, @properties.sample_rate 99 | assert_equal 2, @properties.channels 100 | assert_equal TagLib::MPEG::Header::Version1, @properties.version 101 | assert_equal 3, @properties.layer 102 | assert_equal false, @properties.protection_enabled 103 | assert_equal TagLib::MPEG::Header::JointStereo, @properties.channel_mode 104 | assert_equal false, @properties.copyrighted? 105 | assert_equal true, @properties.original? 106 | end 107 | 108 | context 'Xing header' do 109 | setup do 110 | @xing_header = @properties.xing_header 111 | end 112 | 113 | should 'exist' do 114 | assert_not_nil @xing_header 115 | end 116 | 117 | should 'contain information' do 118 | assert @xing_header.valid? 119 | assert_equal 10, @xing_header.total_frames 120 | assert_equal 4596, @xing_header.total_size 121 | assert_equal TagLib::MPEG::XingHeader::Xing, @xing_header.type 122 | end 123 | end 124 | end 125 | 126 | context 'tag' do 127 | setup do 128 | @tag = @file.tag 129 | end 130 | 131 | should 'exist' do 132 | refute_nil @tag 133 | assert_equal TagLib::Tag, @tag.class 134 | end 135 | 136 | should 'have basic properties' do 137 | refute @tag.empty? 138 | 139 | assert_equal 'Title', @tag.title 140 | assert_equal 'Artist', @tag.artist 141 | assert_equal 'Album', @tag.album 142 | assert_equal 'Comment', @tag.comment 143 | assert_equal 'Pop', @tag.genre 144 | assert_equal 2011, @tag.year 145 | assert_equal 7, @tag.track 146 | end 147 | end 148 | 149 | context 'ID3V1 tag' do 150 | setup do 151 | @tag = @file.id3v1_tag(false) 152 | end 153 | 154 | should 'exist' do 155 | assert @file.id3v1_tag? 156 | refute @file.id3v2_tag? 157 | refute @file.ape_tag? 158 | 159 | assert_not_nil @tag 160 | assert_equal TagLib::ID3v1::Tag, @tag.class 161 | end 162 | 163 | should 'have basic properties' do 164 | refute @tag.empty? 165 | 166 | assert_equal 'Title', @tag.title 167 | assert_equal 'Artist', @tag.artist 168 | assert_equal 'Album', @tag.album 169 | assert_equal 'Comment', @tag.comment 170 | assert_equal 'Pop', @tag.genre 171 | assert_equal 2011, @tag.year 172 | assert_equal 7, @tag.track 173 | end 174 | end 175 | 176 | teardown do 177 | @file.close 178 | @file = nil 179 | end 180 | end 181 | 182 | context 'TagLib::MPEG::File' do 183 | should 'have open method' do 184 | title = nil 185 | TagLib::MPEG::File.open('test/data/sample.mp3', false) do |file| 186 | title = file.tag.title 187 | end 188 | assert_equal 'Dummy Title', title 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /test/tag_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestTag < Test::Unit::TestCase 6 | context 'The sample.mp3 file' do 7 | setup do 8 | @fileref = TagLib::FileRef.new('test/data/sample.mp3', false) 9 | @tag = @fileref.tag 10 | end 11 | 12 | should 'have basic tag information' do 13 | assert_equal 'Dummy Title', @tag.title 14 | assert_equal 'Dummy Artist', @tag.artist 15 | assert_equal 'Dummy Album', @tag.album 16 | assert_equal 'Dummy Comment', @tag.comment 17 | assert_equal 'Pop', @tag.genre 18 | assert_equal 2000, @tag.year 19 | assert_equal 1, @tag.track 20 | assert_equal false, @tag.empty? 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/unicode_filename_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | require 'fileutils' 5 | 6 | class TestUnicodeFilename < Test::Unit::TestCase 7 | SRC_FILE = 'test/data/vorbis.oga' 8 | # That's "hello-" followed by "ni hao" in UTF-8 9 | DST_FILE = "test/data/hello-\xE4\xBD\xA0\xE5\xA5\xBD.oga".dup # mutable 10 | 11 | context 'TagLib::FileRef' do 12 | setup do 13 | DST_FILE.force_encoding('UTF-8') if HAVE_ENCODING 14 | FileUtils.cp SRC_FILE, DST_FILE 15 | @fileref = TagLib::FileRef.new(DST_FILE, false) 16 | @tag = @fileref.tag 17 | end 18 | 19 | should 'be possible to read' do 20 | assert_not_nil @tag 21 | assert_equal 'Title', @tag.title 22 | end 23 | 24 | teardown do 25 | @fileref.close 26 | FileUtils.rm DST_FILE 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/vorbis_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestVorbisFile < Test::Unit::TestCase 6 | context 'The vorbis.oga file' do 7 | setup do 8 | @file = TagLib::Ogg::Vorbis::File.new('test/data/vorbis.oga') 9 | end 10 | 11 | should 'have a tag' do 12 | tag = @file.tag 13 | assert_not_nil tag 14 | assert_equal TagLib::Ogg::XiphComment, tag.class 15 | end 16 | 17 | context 'audio properties' do 18 | setup do 19 | @properties = @file.audio_properties 20 | end 21 | 22 | should 'exist' do 23 | assert_not_nil @properties 24 | end 25 | 26 | should 'contain basic information' do 27 | assert_equal 0, @properties.length_in_seconds # file is short 28 | assert_includes [371, 76], @properties.bitrate 29 | assert_equal 44100, @properties.sample_rate 30 | assert_equal 2, @properties.channels 31 | end 32 | 33 | should 'contain vorbis-specific information' do 34 | assert_equal 0, @properties.vorbis_version 35 | assert_equal 0, @properties.bitrate_maximum 36 | assert_equal 64000, @properties.bitrate_nominal 37 | assert_equal 0, @properties.bitrate_minimum 38 | end 39 | end 40 | 41 | teardown do 42 | @file.close 43 | @file = nil 44 | end 45 | end 46 | 47 | context 'TagLib::Ogg::Vorbis::File' do 48 | should 'have open method' do 49 | title = nil 50 | TagLib::Ogg::Vorbis::File.open('test/data/vorbis.oga', false) do |file| 51 | title = file.tag.title 52 | end 53 | assert_equal 'Title', title 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/vorbis_tag_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class TestVorbisTag < Test::Unit::TestCase 6 | context 'The vorbis.oga file tag' do 7 | setup do 8 | @file = TagLib::Ogg::Vorbis::File.new('test/data/vorbis.oga') 9 | @tag = @file.tag 10 | end 11 | 12 | should 'contain basic tag information' do 13 | assert_equal 'Title', @tag.title 14 | assert_equal 'Artist', @tag.artist 15 | assert_equal 'Album', @tag.album 16 | # Use DESCRIPTION if it exists, otherwise COMMENT. 17 | assert_equal 'Test file', @tag.comment 18 | assert_equal 'Pop', @tag.genre 19 | assert_equal 2011, @tag.year 20 | assert_equal 7, @tag.track 21 | assert_equal false, @tag.empty? 22 | end 23 | 24 | should 'have contains? method' do 25 | assert @tag.contains?('TITLE') 26 | assert !@tag.contains?('DOESNTEXIST') 27 | end 28 | 29 | should 'have field_count' do 30 | assert_equal 18, @tag.field_count 31 | end 32 | 33 | should 'have vendor_id' do 34 | assert_equal 'Xiph.Org libVorbis I 20101101 (Schaufenugget)', @tag.vendor_id 35 | end 36 | 37 | context 'fields' do 38 | setup do 39 | @fields = @tag.field_list_map 40 | end 41 | 42 | should 'exist' do 43 | assert_not_nil @fields 44 | end 45 | 46 | should 'be usable as a Hash' do 47 | assert_equal ['Title'], @fields['TITLE'] 48 | assert_nil @fields['DOESNTEXIST'] 49 | end 50 | 51 | should 'be able to return more than one value for a key' do 52 | assert_equal %w[A B], @fields['MULTIPLE'] 53 | end 54 | end 55 | 56 | should 'support add_field with replace' do 57 | @tag.add_field('TITLE', 'New Title') 58 | assert_equal ['New Title'], @tag.field_list_map['TITLE'] 59 | end 60 | 61 | should 'support add_field without replace' do 62 | replace = false 63 | @tag.add_field('TITLE', 'Additional Title', replace) 64 | assert_equal ['Title', 'Additional Title'], @tag.field_list_map['TITLE'] 65 | end 66 | 67 | should 'support remove_fields' do 68 | assert @tag.contains?('MULTIPLE') 69 | @tag.remove_fields('MULTIPLE') 70 | refute @tag.contains?('MULTIPLE') 71 | end 72 | 73 | should 'support remove_all_fields' do 74 | refute_equal 0, @tag.field_count 75 | @tag.remove_all_fields 76 | # remove_all_fields() do not remove pictures 77 | assert_equal 1, @tag.field_count 78 | end 79 | 80 | should 'have pictures' do 81 | refute_empty @tag.picture_list 82 | end 83 | 84 | context 'first picture' do 85 | setup do 86 | @picture = @tag.picture_list.first 87 | end 88 | 89 | should 'be a TagLib::FLAC::Picture,' do 90 | assert_equal TagLib::FLAC::Picture, @picture.class 91 | end 92 | 93 | should 'have meta-data' do 94 | assert_equal TagLib::FLAC::Picture::FrontCover, @picture.type 95 | assert_equal 'image/jpeg', @picture.mime_type 96 | assert_equal 'Globe', @picture.description 97 | assert_equal 90, @picture.width 98 | assert_equal 90, @picture.height 99 | assert_equal 24, @picture.color_depth 100 | assert_equal 0, @picture.num_colors 101 | end 102 | 103 | should 'have data' do 104 | picture_data = File.open('test/data/globe_east_90.jpg', 'rb') { |f| f.read } 105 | assert_equal picture_data, @picture.data 106 | end 107 | end 108 | 109 | should 'support removing a picture' do 110 | refute_empty @tag.picture_list 111 | @tag.remove_picture(@tag.picture_list.first) 112 | assert_empty @tag.picture_list 113 | end 114 | 115 | should 'support removing all pictures' do 116 | refute_empty @tag.picture_list 117 | @tag.remove_all_pictures 118 | assert_empty @tag.picture_list 119 | end 120 | 121 | teardown do 122 | @file.close 123 | @file = nil 124 | end 125 | end 126 | 127 | context 'The XiphComment class' do 128 | should 'support check_key' do 129 | refute TagLib::Ogg::XiphComment.check_key('') 130 | refute TagLib::Ogg::XiphComment.check_key('something=') 131 | refute TagLib::Ogg::XiphComment.check_key('something~') 132 | assert TagLib::Ogg::XiphComment.check_key('something') 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /test/wav_examples_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class WAVExamples < Test::Unit::TestCase 6 | DATA_FILE_PREFIX = 'test/data/wav-' 7 | 8 | context 'TagLib::RIFF::WAV::File' do 9 | should 'Run TagLib::RIFF::WAV::File examples' do 10 | # Reading the title 11 | title = TagLib::RIFF::WAV::File.open("#{DATA_FILE_PREFIX}sample.wav") do |file| 12 | file.tag.title 13 | end 14 | 15 | # Reading WAV-specific audio properties 16 | TagLib::RIFF::WAV::File.open("#{DATA_FILE_PREFIX}sample.wav") do |file| 17 | file.audio_properties.sample_rate #=> 8 18 | end 19 | 20 | # Saving ID3v2 cover-art to disk 21 | TagLib::RIFF::WAV::File.open("#{DATA_FILE_PREFIX}sample.wav") do |file| 22 | id3v2_tag = file.id3v2_tag 23 | cover = id3v2_tag.frame_list('APIC').first 24 | ext = cover.mime_type.rpartition('/')[2] 25 | File.open("#{DATA_FILE_PREFIX}cover-art.#{ext}", 'wb') { |f| f.write cover.picture } 26 | end 27 | 28 | # checks 29 | assert_equal 'WAV Dummy Track Title', title 30 | assert_equal true, File.exist?("#{DATA_FILE_PREFIX}cover-art.jpeg") 31 | FileUtils.rm("#{DATA_FILE_PREFIX}cover-art.jpeg") 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/wav_file_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class WAVFileTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/wav-sample.wav' 7 | PICTURE_FILE = 'test/data/globe_east_540.jpg' 8 | 9 | context 'TagLib::RIFF::WAV::File' do 10 | setup do 11 | @file = TagLib::RIFF::WAV::File.new(SAMPLE_FILE) 12 | end 13 | 14 | should 'open' do 15 | assert_not_nil @file 16 | end 17 | 18 | context 'audio properties' do 19 | setup do 20 | @properties = @file.audio_properties 21 | end 22 | 23 | should 'exist' do 24 | assert_not_nil @properties 25 | end 26 | 27 | should 'contain basic information' do 28 | assert_equal 0, @properties.length_in_seconds 29 | assert_equal 698, @properties.length_in_milliseconds 30 | assert_equal 88, @properties.bitrate 31 | assert_equal 11025, @properties.sample_rate 32 | assert_equal 1, @properties.channels 33 | end 34 | 35 | should 'contain WAV-specific information' do 36 | assert_equal 8, @properties.bits_per_sample 37 | assert_equal TagLib::RIFF::WAV::FORMAT_PCM, @properties.format 38 | end 39 | end 40 | 41 | context 'ID3V2 tag' do 42 | setup do 43 | @tag = @file.id3v2_tag 44 | end 45 | 46 | should 'exist' do 47 | assert @file.id3v2_tag? 48 | assert_not_nil @tag 49 | assert_equal TagLib::ID3v2::Tag, @tag.class 50 | end 51 | 52 | should 'not have an Info tag' do 53 | refute @file.info_tag? 54 | end 55 | 56 | should 'contain basic tag information' do 57 | assert_equal 'WAV Dummy Track Title', @tag.title 58 | assert_equal 'WAV Dummy Artist Name', @tag.artist 59 | assert_equal 'WAV Dummy Album Title', @tag.album 60 | assert_equal 'WAV Dummy Comment', @tag.comment 61 | assert_equal 'Jazz', @tag.genre 62 | assert_equal 2014, @tag.year 63 | assert_equal 5, @tag.track 64 | assert_equal false, @tag.empty? 65 | end 66 | 67 | context 'APIC frame' do 68 | setup do 69 | @picture_data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 70 | @apic = @tag.frame_list('APIC').first 71 | end 72 | 73 | should 'exist' do 74 | assert_not_nil @apic 75 | assert_equal TagLib::ID3v2::AttachedPictureFrame, @apic.class 76 | end 77 | 78 | should 'have a type' do 79 | assert_equal TagLib::ID3v2::AttachedPictureFrame::FrontCover, @apic.type 80 | end 81 | 82 | should 'have a mime type' do 83 | assert_equal 'image/jpeg', @apic.mime_type 84 | end 85 | 86 | should 'have picture bytes' do 87 | assert_equal 61_649, @apic.picture.size 88 | assert_equal @picture_data, @apic.picture 89 | end 90 | end 91 | end 92 | 93 | teardown do 94 | @file.close 95 | @file = nil 96 | end 97 | end 98 | 99 | context 'TagLib::RIFF::WAV::File.open' do 100 | should 'have open method' do 101 | title = nil 102 | TagLib::RIFF::WAV::File.open(SAMPLE_FILE, false) do |file| 103 | title = file.tag.title 104 | end 105 | assert_equal 'WAV Dummy Track Title', title 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /test/wav_file_write_test.rb: -------------------------------------------------------------------------------- 1 | # frozen-string-literal: true 2 | 3 | require File.join(File.dirname(__FILE__), 'helper') 4 | 5 | class WAVFileWriteTest < Test::Unit::TestCase 6 | SAMPLE_FILE = 'test/data/wav-sample.wav' 7 | OUTPUT_FILE = 'test/data/_output.wav' 8 | PICTURE_FILE = 'test/data/globe_east_90.jpg' 9 | 10 | def reloaded(&block) 11 | TagLib::RIFF::WAV::File.open(OUTPUT_FILE, false, &block) 12 | end 13 | 14 | context 'TagLib::RIFF::WAV::File' do 15 | setup do 16 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 17 | @file = TagLib::RIFF::WAV::File.new(OUTPUT_FILE, false) 18 | end 19 | 20 | should 'be able to save the title' do 21 | tag = @file.tag 22 | assert_not_nil tag 23 | tag.title = 'New Title' 24 | success = @file.save 25 | assert success 26 | @file.close 27 | @file = nil 28 | 29 | written_title = reloaded do |file| 30 | file.tag.title 31 | end 32 | assert_equal 'New Title', written_title 33 | end 34 | 35 | should 'have one picture frame' do 36 | assert_equal 2, @file.id3v2_tag.frame_list('APIC').size 37 | end 38 | 39 | should 'be able to remove all picture frames' do 40 | @file.id3v2_tag.remove_frames('APIC') 41 | success = @file.save 42 | assert success 43 | @file.close 44 | @file = nil 45 | 46 | reloaded do |file| 47 | assert_equal 0, file.id3v2_tag.frame_list('APIC').size 48 | end 49 | end 50 | 51 | should 'be able to add a picture frame' do 52 | picture_data = File.open(PICTURE_FILE, 'rb') { |f| f.read } 53 | 54 | apic = TagLib::ID3v2::AttachedPictureFrame.new 55 | apic.mime_type = 'image/jpeg' 56 | apic.description = 'desc' 57 | apic.text_encoding = TagLib::String::UTF8 58 | apic.picture = picture_data 59 | apic.type = TagLib::ID3v2::AttachedPictureFrame::BackCover 60 | 61 | @file.id3v2_tag.add_frame(apic) 62 | success = @file.save 63 | assert success 64 | @file.close 65 | @file = nil 66 | 67 | reloaded do |file| 68 | assert_equal 3, file.id3v2_tag.frame_list('APIC').size 69 | end 70 | 71 | reloaded do |file| 72 | written_apic = file.id3v2_tag.frame_list('APIC')[2] 73 | assert_equal 'image/jpeg', written_apic.mime_type 74 | assert_equal 'desc', written_apic.description 75 | assert_equal picture_data, written_apic.picture 76 | end 77 | end 78 | 79 | teardown do 80 | if @file 81 | @file.close 82 | @file = nil 83 | end 84 | FileUtils.rm OUTPUT_FILE 85 | end 86 | end 87 | 88 | context 'TagLib::RIFF::WAV::File.strip' do 89 | setup do 90 | FileUtils.cp SAMPLE_FILE, OUTPUT_FILE 91 | @file = TagLib::RIFF::WAV::File.new(OUTPUT_FILE) 92 | end 93 | 94 | should 'update the file immediately' do 95 | assert @file.id3v2_tag? 96 | 97 | @file.strip(TagLib::RIFF::WAV::File::ID3v2) 98 | 99 | reloaded do |_file| 100 | refute @file.id3v2_tag? 101 | end 102 | end 103 | 104 | teardown do 105 | if @file 106 | @file.close 107 | @file = nil 108 | end 109 | FileUtils.rm OUTPUT_FILE 110 | end 111 | end 112 | end 113 | --------------------------------------------------------------------------------