id3taginator 0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.idea/.gitignore +8 -0
- data/.idea/ID3Taginator.iml +73 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +408 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/id3taginator.gemspec +37 -0
- data/lib/id3taginator/audio_file.rb +136 -0
- data/lib/id3taginator/errors/id3_tag_error.rb +12 -0
- data/lib/id3taginator/extensions/argument_check.rb +88 -0
- data/lib/id3taginator/extensions/comparable.rb +28 -0
- data/lib/id3taginator/extensions/encodable.rb +215 -0
- data/lib/id3taginator/extensions/optionable.rb +73 -0
- data/lib/id3taginator/frames/buffer/entities/buffer.rb +26 -0
- data/lib/id3taginator/frames/buffer/rbuf_recommended_buffer_size_frame.rb +59 -0
- data/lib/id3taginator/frames/buffer_frames.rb +32 -0
- data/lib/id3taginator/frames/comment/comm_comment_frame.rb +56 -0
- data/lib/id3taginator/frames/comment/entities/comment.rb +26 -0
- data/lib/id3taginator/frames/comment_frames.rb +42 -0
- data/lib/id3taginator/frames/count/entities/popularimeter.rb +26 -0
- data/lib/id3taginator/frames/count/pcnt_play_counter_frame.rb +40 -0
- data/lib/id3taginator/frames/count/popm_popularimeter_frame.rb +52 -0
- data/lib/id3taginator/frames/count_frames.rb +51 -0
- data/lib/id3taginator/frames/custom_frame.rb +39 -0
- data/lib/id3taginator/frames/custom_frames.rb +54 -0
- data/lib/id3taginator/frames/encryption/aenc_audio_encryption.rb +54 -0
- data/lib/id3taginator/frames/encryption/encr_encryption_method_frame.rb +49 -0
- data/lib/id3taginator/frames/encryption/entities/audio_encryption.rb +28 -0
- data/lib/id3taginator/frames/encryption/entities/encryption_method.rb +26 -0
- data/lib/id3taginator/frames/encryption_frames.rb +77 -0
- data/lib/id3taginator/frames/frameable.rb +75 -0
- data/lib/id3taginator/frames/geo/entities/encapsulated_object.rb +28 -0
- data/lib/id3taginator/frames/geo/geob_general_encapsulated_object_frame.rb +59 -0
- data/lib/id3taginator/frames/geo_frames.rb +41 -0
- data/lib/id3taginator/frames/grouping/entities/group_identification.rb +26 -0
- data/lib/id3taginator/frames/grouping/grid_group_identification_frame.rb +49 -0
- data/lib/id3taginator/frames/grouping/grp1_grouping_frame.rb +40 -0
- data/lib/id3taginator/frames/grouping_frames.rb +61 -0
- data/lib/id3taginator/frames/has_id.rb +51 -0
- data/lib/id3taginator/frames/id3v23_frame_flags.rb +64 -0
- data/lib/id3taginator/frames/id3v24_frame_flags.rb +80 -0
- data/lib/id3taginator/frames/id3v2_frame.rb +249 -0
- data/lib/id3taginator/frames/id3v2_frame_factory.rb +98 -0
- data/lib/id3taginator/frames/ipl/entities/involved_person.rb +24 -0
- data/lib/id3taginator/frames/ipl/ipls_involved_people_frame.rb +48 -0
- data/lib/id3taginator/frames/ipl_frames.rb +31 -0
- data/lib/id3taginator/frames/lyrics/entities/unsync_lyrics.rb +26 -0
- data/lib/id3taginator/frames/lyrics/uslt_unsync_lyrics_frame.rb +56 -0
- data/lib/id3taginator/frames/lyrics_frames.rb +42 -0
- data/lib/id3taginator/frames/mcdi/mcdi_music_cd_identifier_frame.rb +40 -0
- data/lib/id3taginator/frames/mcdi_frames.rb +28 -0
- data/lib/id3taginator/frames/picture/apic_picture_frame.rb +106 -0
- data/lib/id3taginator/frames/picture/entities/picture.rb +32 -0
- data/lib/id3taginator/frames/picture_frames.rb +51 -0
- data/lib/id3taginator/frames/private/entities/private_frame.rb +24 -0
- data/lib/id3taginator/frames/private/priv_private_frame.rb +45 -0
- data/lib/id3taginator/frames/private_frames.rb +40 -0
- data/lib/id3taginator/frames/text/entities/copyright.rb +24 -0
- data/lib/id3taginator/frames/text/entities/date.rb +24 -0
- data/lib/id3taginator/frames/text/entities/part_of_set.rb +24 -0
- data/lib/id3taginator/frames/text/entities/time.rb +24 -0
- data/lib/id3taginator/frames/text/entities/track_number.rb +24 -0
- data/lib/id3taginator/frames/text/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/text/talb_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tbpm_bpm_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tcom_composer_frame.rb +42 -0
- data/lib/id3taginator/frames/text/tcon_genre_frame.rb +104 -0
- data/lib/id3taginator/frames/text/tcop_copyright_frame.rb +55 -0
- data/lib/id3taginator/frames/text/tdat_date_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tdly_playlist_delay_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tenc_encoded_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/text_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tflt_file_type_frame.rb +71 -0
- data/lib/id3taginator/frames/text/time_time_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tit1_content_group_description_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit2_title_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit3_subtitle_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tkey_initial_key_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tlan_language_frame.rb +48 -0
- data/lib/id3taginator/frames/text/tlen_length_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tmed_media_type_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toal_original_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tofn_original_filename_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toly_original_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tope_original_artists_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tory_original_release_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text/town_file_owner_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe1_artist_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tpe2_album_artist_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe3_conductor_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe4_modified_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpos_part_of_set_frame.rb +50 -0
- data/lib/id3taginator/frames/text/tpub_publisher_frame.rb +40 -0
- data/lib/id3taginator/frames/text/trck_track_number_frame.rb +50 -0
- data/lib/id3taginator/frames/text/trda_recording_dates_frame.rb +42 -0
- data/lib/id3taginator/frames/text/trsn_internet_radio_station_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsiz_size_frame.rb +41 -0
- data/lib/id3taginator/frames/text/tsoa_album_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsop_performer_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsot_title_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsrc_isrc_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsse_encoder_frame.rb +40 -0
- data/lib/id3taginator/frames/text/txxx_user_text_info_frame.rb +51 -0
- data/lib/id3taginator/frames/text/tyer_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text_frames.rb +840 -0
- data/lib/id3taginator/frames/tos/entities/ownership.rb +26 -0
- data/lib/id3taginator/frames/tos/entities/terms_of_use.rb +24 -0
- data/lib/id3taginator/frames/tos/owne_ownership_frame.rb +53 -0
- data/lib/id3taginator/frames/tos/user_terms_of_use_frame.rb +49 -0
- data/lib/id3taginator/frames/tos_frames.rb +54 -0
- data/lib/id3taginator/frames/ufid/entities/ufid_info.rb +24 -0
- data/lib/id3taginator/frames/ufid/ufid_unique_file_identifier_frame.rb +47 -0
- data/lib/id3taginator/frames/ufid_frames.rb +40 -0
- data/lib/id3taginator/frames/url/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/url/wcom_commercial_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wcop_copyright_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woaf_official_file_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woar_official_artist_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woas_official_source_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wors_official_radio_station_homepage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpay_payment_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpub_official_publisher_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wxxx_user_url_link_frame.rb +50 -0
- data/lib/id3taginator/frames/url_frames.rb +195 -0
- data/lib/id3taginator/genres.rb +168 -0
- data/lib/id3taginator/header/id3v23_extended_header.rb +37 -0
- data/lib/id3taginator/header/id3v24_extended_header.rb +100 -0
- data/lib/id3taginator/header/id3v2_flags.rb +64 -0
- data/lib/id3taginator/id3v1_tag.rb +156 -0
- data/lib/id3taginator/id3v22_tag.rb +30 -0
- data/lib/id3taginator/id3v23_tag.rb +63 -0
- data/lib/id3taginator/id3v24_tag.rb +75 -0
- data/lib/id3taginator/id3v2_tag.rb +241 -0
- data/lib/id3taginator/options/options.rb +33 -0
- data/lib/id3taginator/util/compress_util.rb +25 -0
- data/lib/id3taginator/util/math_util.rb +68 -0
- data/lib/id3taginator/util/sync_util.rb +65 -0
- data/lib/id3taginator.rb +449 -0
- metadata +198 -0
data/README.md
ADDED
@@ -0,0 +1,408 @@
|
|
1
|
+
# Id3taginator
|
2
|
+
|
3
|
+
- [Introduction](#introduction)
|
4
|
+
- [Installation](#installation)
|
5
|
+
- [Implemented Frames](#implemented-frames)
|
6
|
+
- [Usage](#usage)
|
7
|
+
* [Read Tags](#read-tags)
|
8
|
+
* [Modify Tag](#modify-tag)
|
9
|
+
+ [Remove Tag](#remove-tag)
|
10
|
+
+ [Modify existing Frame](#modify-existing-frame)
|
11
|
+
+ [Create new Tag](#create-new-tag)
|
12
|
+
- [What is the difference?](#what-is-the-difference-)
|
13
|
+
* [Write Audio File with modified Tags](#write-audio-file-with-modified-tags)
|
14
|
+
* [Options](#options)
|
15
|
+
- [default_encode_dest](#default-encode-dest)
|
16
|
+
- [default_decode_dest](#default-decode-dest)
|
17
|
+
- [padding_bytes](#padding-bytes)
|
18
|
+
- [ignore_v23_frame_error](#ignore-v23-frame-error)
|
19
|
+
- [ignore_v24_frame_error](#ignore-v24-frame-error)
|
20
|
+
- [add_size_frame](#add-size-frame)
|
21
|
+
+ [How to set Options](#how-to-set-options)
|
22
|
+
* [Create Entities](#create-entities)
|
23
|
+
* [Custom Frames](#custom-frames)
|
24
|
+
- [Development](#development)
|
25
|
+
- [Contributing](#contributing)
|
26
|
+
- [License](#license)
|
27
|
+
- [Code of Conduct](#code-of-conduct)
|
28
|
+
|
29
|
+
## Introduction
|
30
|
+
|
31
|
+
Id3Taginator is a ID3v1, ID3v2.2/3/4 [tag reader](https://en.wikipedia.org/wiki/ID3) and writer fully written in Ruby and does not
|
32
|
+
rely on TagLib or any other 3rd party library to read/write iD3 Tags. It aims to offer a simple
|
33
|
+
way to read and write ID3Tags.
|
34
|
+
It follows the specifications as seen here:
|
35
|
+
* ID3v1 - https://id3.org/ID3v1
|
36
|
+
* ID3v2.2 - https://id3.org/id3v2-00
|
37
|
+
* Id3v2.3 - https://id3.org/id3v2.3.0
|
38
|
+
* Id3v2.4 - https://id3.org/id3v2.4.0-structure / https://id3.org/id3v2.4.0-frames
|
39
|
+
|
40
|
+
It can write ID3v1 and ID3v2 to the same file. For ID3v2 only 1 version can be used.
|
41
|
+
It is recommended to stick to ID3v2.3, because for now, it seems to be the most supported version.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
from [rubygems](https://rubygems.org/gems/id3taginator)
|
46
|
+
|
47
|
+
```shell
|
48
|
+
gem 'id3taginator'
|
49
|
+
```
|
50
|
+
or from the sources using
|
51
|
+
```shell
|
52
|
+
gem build id3taginator.gemspec
|
53
|
+
```
|
54
|
+
and then execute
|
55
|
+
```shell
|
56
|
+
gem install id3taginator-x.x.x.gem
|
57
|
+
```
|
58
|
+
|
59
|
+
## Implemented Frames
|
60
|
+
|
61
|
+
The following frames are implemented and fully supported. Not implemented frames can still be added by using a custom frame.
|
62
|
+
See more about custom frames in the _Usage_ part. Most Frames are self explaining. For other documentation about the frames and their meaning,
|
63
|
+
please check the documentation - [ID3v1](https://id3.org/ID3v1), [ID3v2.2](https://id3.org/id3v2-00),
|
64
|
+
[Id3v2.3](https://id3.org/id3v2.3.0), [Id3v2.4](https://id3.org/id3v2.4.0-structure)
|
65
|
+
|
66
|
+
**Hint:** If a frame is currently not implemented, it can still be read and written using Custom Frames.
|
67
|
+
In this case, if an MP3 file is written that contains a not implemented frame, that frame will simply be saved as a
|
68
|
+
Custom Frame, can be modified as such and will be written as it was once the file is saved.
|
69
|
+
|
70
|
+
| Implemented | Frame | Description | ID3v2.2 | ID3v2.3 | ID3v2.4 |
|
71
|
+
| :---------: | ----- | --------------------------------------------------- | :-----: | :-----: | :-----: |
|
72
|
+
| x | AENC/CRA | Audio encryption | x | x | x |
|
73
|
+
| x | APIC/PIC | Attached picture | x | x | x |
|
74
|
+
| | ASPI | Audio seek point index | | | x |
|
75
|
+
| x | COMM/COM | Comments | x | x | x |
|
76
|
+
| | COMR | Commercial frame | | x | x |
|
77
|
+
| | CRM | Encrypted meta frame | x | | |
|
78
|
+
| x | ENCR | Encryption method registration | | x | x |
|
79
|
+
| | EQUA/EQU | Equalization | x | x | |
|
80
|
+
| | EQU2 | Equalization (2) | | | x |
|
81
|
+
| | ETCO/ETC | Event timing codes | x | x | x |
|
82
|
+
| x | GEOB/GEO | General encapsulated object | x | x | x |
|
83
|
+
| x | GRID | Group identification registration | | x | x |
|
84
|
+
| x | GRP1 | iTunes Grouping | | x | x |
|
85
|
+
| x | IPLS/PLS | Involved people list | x | x | x |
|
86
|
+
| | LINK/LNK | Linked information | x | x | x |
|
87
|
+
| x | MCDI/MCI | Music CD identifier | x | x | x |
|
88
|
+
| | MLLT/MLL | MPEG location lookup table | x | x | x |
|
89
|
+
| x | PCNT/CNT | Play counter | x | x | x |
|
90
|
+
| x | POPM/POP | Popularimeter | x | x | x |
|
91
|
+
| | POSS | Position synchronisation frame | | x | x |
|
92
|
+
| x | RBUF/BUF | Recommended buffer size | x | x | x |
|
93
|
+
| | RVAD/RVA | Relative volume adjustment | x | x | |
|
94
|
+
| | RVA2 | Relative volume adjustment (2) | | | x |
|
95
|
+
| | RVRB/REV | Reverb | x | x | x |
|
96
|
+
| | SEEK | Seek frame | | | x |
|
97
|
+
| | SIGN | Signature frame | | | x |
|
98
|
+
| | SYLT/SLT | Synchronized lyric/text | x | x | x |
|
99
|
+
| | SYTC/STC | Synchronized tempo codes | x | x | x |
|
100
|
+
| x | OWNE | Ownership frame | | x | x |
|
101
|
+
| x | PRIV | Private frame | | x | x |
|
102
|
+
| x | TALB/TAL | Album/Movie/Show title | x | x | x |
|
103
|
+
| x | TBPM/TBP | BPM (beats per minute) | x | x | x |
|
104
|
+
| x | TCOM/TCM | Composer | x | x | x |
|
105
|
+
| x | TCON/TCO | Content type | x | x | x |
|
106
|
+
| x | TCOP/TCR | Copyright message | x | x | x |
|
107
|
+
| x | TDAT/TDA | Date | x | x | x |
|
108
|
+
| | TDEN | Encoding time | | | x |
|
109
|
+
| x | TDLY/TDY | Playlist delay | x | x | x |
|
110
|
+
| | TDOR | Original release time | | | x |
|
111
|
+
| | TDRC | Recording time | | | x |
|
112
|
+
| | TDRL | Release time | | | x |
|
113
|
+
| | TDTG | Tagging time | | | x |
|
114
|
+
| x | TENC/TEN | Encoded by | x | x | x |
|
115
|
+
| x | TEXT/TXT | Lyricist/Text writer | x | x | x |
|
116
|
+
| x | TFLT/TFT | File type | x | x | x |
|
117
|
+
| x | TIME/TIM | Time | x | x | x |
|
118
|
+
| x | TIT1/TT1 | Content group description | x | x | x |
|
119
|
+
| x | TIT2/TT2 | Title/songname/content description | x | x | x |
|
120
|
+
| x | TIT3/TT3 | Subtitle/Description refinement | x | x | x |
|
121
|
+
| x | TKEY/TKE | Initial key | x | x | x |
|
122
|
+
| x | TLAN/TLA | Language(s) | x | x | x |
|
123
|
+
| x | TLEN/TLE | Length | x | x | x |
|
124
|
+
| | TMCL | Musician credits list | | | x |
|
125
|
+
| x | TMED/TMT | Media type | x | x | x |
|
126
|
+
| | TMOO | Mood | | | x |
|
127
|
+
| x | TOAL/TOT | Original album/movie/show title | x | x | x |
|
128
|
+
| x | TOFN/TOF | Original filename | x | x | x |
|
129
|
+
| x | TOLY/TOL | Original lyricist(s)/text writer(s) | x | x | x |
|
130
|
+
| x | TOPE/TOA | Original artist(s)/performer(s) | x | x | x |
|
131
|
+
| x | TORY/TOR | Original release year | x | x | x |
|
132
|
+
| x | TOWN | File owner/licensee | | x | x |
|
133
|
+
| x | TPE1/TP1 | Lead performer(s)/Soloist(s) | x | x | x |
|
134
|
+
| x | TPE2/TP2 | Band/orchestra/accompaniment | x | x | x |
|
135
|
+
| x | TPE3/TP3 | Conductor/performer refinement | x | x | x |
|
136
|
+
| x | TPE4/TP4 | Interpreted, remixed, or otherwise modified by | x | x | x |
|
137
|
+
| x | TPOS/TPA | Part of a set | x | x | x |
|
138
|
+
| | TPRO | Produced notice | | | x |
|
139
|
+
| x | TPUB/TPB | Publisher | x | x | x |
|
140
|
+
| x | TRCK/TRK | Track number/Position in set | x | x | x |
|
141
|
+
| x | TRDA/TRD | Recording dates | x | x | x |
|
142
|
+
| x | TRSN | Internet radio station name | | x | x |
|
143
|
+
| | TRSO | Internet radio station owner | | | x |
|
144
|
+
| x | TSIZ/TSI | Size | x | x | |
|
145
|
+
| x | TSOA | Album sort order | | | x |
|
146
|
+
| x | TSOP | Performer sort order | | | x |
|
147
|
+
| x | TSOT | Title sort order | | | x |
|
148
|
+
| x | TSRC/TRC | ISRC (international standard recording code) | x | x | x |
|
149
|
+
| x | TSSE/TSS | Software/Hardware and settings used for encoding | x | x | x |
|
150
|
+
| | TSST | Set subtitle | | | x |
|
151
|
+
| x | TXXX/TXX | User defined text information frame | x | x | x |
|
152
|
+
| x | TYER/TYE | Year | x | x | x |
|
153
|
+
| x | UFID/UFI | Unique file identifier | x | x | x |
|
154
|
+
| x | USER | Terms of use | | x | x |
|
155
|
+
| x | USLT/ULT | Unsynchronized lyric/text transcription | x | x | x |
|
156
|
+
| x | WCOM/WCM | Commercial information | x | x | x |
|
157
|
+
| x | WCOP/WCP | Copyright/Legal information | x | x | x |
|
158
|
+
| x | WOAF/WAF | Official audio file webpage | x | x | x |
|
159
|
+
| x | WOAR/WAR | Official artist/performer webpage | x | x | x |
|
160
|
+
| x | WOAS/WAS | Official audio source webpage | x | x | x |
|
161
|
+
| x | WORS | Official internet radio station homepage | | x | x |
|
162
|
+
| x | WPAY | Payment | | x | x |
|
163
|
+
| x | WPUB/WPB | Publishers official webpage | x | x | x |
|
164
|
+
| x | WXXX/WXX | User defined URL link frame | x | x | x |
|
165
|
+
|
166
|
+
## Usage
|
167
|
+
|
168
|
+
The usage is pretty straight forward. The audio file will be read and parsed, and provides access to all
|
169
|
+
available frames, allows to add or remove frames and finally, write the file.
|
170
|
+
|
171
|
+
To modify and/or read a tag, simply create an audio file:
|
172
|
+
```ruby
|
173
|
+
audio_file = Id3Taginator.build_by_path('path/to/audio_file')
|
174
|
+
```
|
175
|
+
this will automatically parse the ID3 tags, if an ID3tag is present.
|
176
|
+
|
177
|
+
### Read Tags
|
178
|
+
```ruby
|
179
|
+
v1_tag = audio_file.id3v1_tag
|
180
|
+
v1_tag.title # => 'ID3v1 Title'
|
181
|
+
v1_tag.artist # => 'ID3v1 Artist'
|
182
|
+
v1_tag.album # => 'ID3v1 Album'
|
183
|
+
|
184
|
+
# this can either be a ID3v2.2, ID3v2.3 or ID3v2.4 tag
|
185
|
+
v2_tag = audio_file.id3v2_tag
|
186
|
+
v2_tag.version # => 2.3.0
|
187
|
+
v2_tag.title # => 'ID3v2 Title'
|
188
|
+
v2_tag.artists # => ['ID3v2 Artist1', 'ID3v2 Artist2']
|
189
|
+
v2_tag.album # => 'ID3v2 Album'
|
190
|
+
```
|
191
|
+
|
192
|
+
### Modify Tag
|
193
|
+
|
194
|
+
existing tags can either be modified, or a new tag can be created, or an existing one removed.
|
195
|
+
|
196
|
+
#### Remove Tag
|
197
|
+
```ruby
|
198
|
+
audio_file.remove_id3v1_tag
|
199
|
+
audio_file.remove_id3v2_tag
|
200
|
+
```
|
201
|
+
will remove the ID3vX tags.
|
202
|
+
|
203
|
+
#### Modify existing Frame
|
204
|
+
|
205
|
+
There are already methods defined to modify a frame. Each frame has one. A Frame that is only allowed once, has only one Getter and one Setter.
|
206
|
+
If a Frame is present, the Setter will update the existing Frame. If no Frame it is present, it will create a new Frame.
|
207
|
+
|
208
|
+
If the Frame can be present multiple times, the Setter Method will update the existing frame, if a specific field with the unique identifier is the same (see the ruby doc to find out which field is the unique field).
|
209
|
+
In those cases, the Setter is `field=` or alternatively `add_field`, e.g. `comment=` and `add_comment(...)` are the same.
|
210
|
+
If no Frame with the unique field is present, a new Frame will be added.
|
211
|
+
|
212
|
+
The Getter methods will return nil if the Frame is not present or en empty array, if the frame is not present, but the result is an array.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
v1_tag = audio_file.id3v1_tag
|
216
|
+
v1_tag.title = 'My new Title'
|
217
|
+
v1_tag.title # => 'My new Title'
|
218
|
+
v1_tag.title = nil
|
219
|
+
v1_tag.title # => nil
|
220
|
+
|
221
|
+
v2_tag = audio_file.id3v2_tag
|
222
|
+
v2_tag.title = 'My new Title'
|
223
|
+
v2_tag.title # => 'My new Title'
|
224
|
+
v2_tag.remove_title
|
225
|
+
v2_tag.title # => nil
|
226
|
+
|
227
|
+
v2_tag.languages # => []
|
228
|
+
v2_tag.languages = %w[eng ger]
|
229
|
+
v2_tag.languages # ['eng', 'ger']
|
230
|
+
v2_tag.languages = %w[eng]
|
231
|
+
v2_tag.languages # ['eng']
|
232
|
+
v2_tag.remove_languages
|
233
|
+
v2_tag.languages # => []
|
234
|
+
|
235
|
+
v2_tag.comments # => []
|
236
|
+
v2_tag.add_comment(Id3Taginator.create_comment('eng', 'my descriptor', 'my comment'))
|
237
|
+
v2_tag.comments # => [Comment('eng', 'my descriptor', 'my comment')]
|
238
|
+
v2_tag.add_comment(Id3Taginator.create_comment('eng', 'another descriptor', 'my other comment'))
|
239
|
+
v2_tag.comments # => [Comment('eng', 'my descriptor', 'my comment'), Comment('eng', 'another descriptor', 'my other comment')]
|
240
|
+
v2_tag.add_comment(Id3Taginator.create_comment('eng', 'my descriptor', 'my modified comment'))
|
241
|
+
# descriptor and language are already present, so it will update the comment instead of creating a new one
|
242
|
+
v2_tag.comments # => [Comment('eng', 'my descriptor', 'my modified comment'), Comment('eng', 'another descriptor', 'my other comment')]
|
243
|
+
|
244
|
+
v2_tag.remove_comment('eng', 'my descriptor')
|
245
|
+
v2_tag.comments # => [Comment('eng', 'another descriptor', 'my other comment')]
|
246
|
+
```
|
247
|
+
|
248
|
+
#### Create new Tag
|
249
|
+
|
250
|
+
If no tag is present, or a new one should be created, a few simple methods are present:
|
251
|
+
```ruby
|
252
|
+
audio_file.create_id3v1_tag # => the id3v1_tag
|
253
|
+
audio_file.create_id3v2_2_tag # => the id3v22_tag
|
254
|
+
audio_file.create_id3v2_3_tag # => the id3v23_tag
|
255
|
+
audio_file.create_id3v2_4_tag # => the id3v24_tag
|
256
|
+
```
|
257
|
+
As mentioned before, it is recommended to use ID3v2.3, because it seems to be the most supported for version.
|
258
|
+
|
259
|
+
##### What is the difference?
|
260
|
+
|
261
|
+
ID3v2.2 is the oldest ID3v2 tag, and for example only uses 3 characters for the Frame ID and offers far less Frames as the successors.
|
262
|
+
It should be avoided, because it is kinda outdated.
|
263
|
+
|
264
|
+
ID3v2.3 and ID3v2.4 are very similar. ID3v2.4 added a few more frames, and allows 2 more encodings in comparison to ID3v2.3,
|
265
|
+
but most of the new added frames or changes are not game breaking and most probably not used by most people.
|
266
|
+
Some of the useful new frames like `Album Sort Order` or `Title Sort Order` are officially not part of ID3v2.3, but most players
|
267
|
+
will interpret them nevertheless, as long as they are present. Per default ID3Taginator allows to add those ID3v2.4 only frames even to
|
268
|
+
ID3v2.3 Tags.
|
269
|
+
|
270
|
+
### Write Audio File with modified Tags
|
271
|
+
|
272
|
+
Once a file is modified, it must be written to save the changes. To do this, Id3Taginator provides to 2 methods:
|
273
|
+
```ruby
|
274
|
+
audio_file.write_audio_file('path/to/new/file')
|
275
|
+
```
|
276
|
+
Which writes the modified file to the specified path. The audio data will be cached, so the file can be written to the same
|
277
|
+
path that is used to read the MP3 file, essentially overwriting it.
|
278
|
+
|
279
|
+
The other alternative is to write the result to a byte array represented as a String:
|
280
|
+
|
281
|
+
```ruby
|
282
|
+
audio_file.audio_file_to_bytes
|
283
|
+
```
|
284
|
+
This will return a byte array represented as a String (str.bytes) with the modified audio file, and the decision how to
|
285
|
+
handle it, is completely yours.
|
286
|
+
|
287
|
+
### Options
|
288
|
+
|
289
|
+
The following options are available:
|
290
|
+
##### default_encode_dest
|
291
|
+
This is the encoding that should be used to encode the data if applicable. For example most Text Frames are encoded
|
292
|
+
and the encoding byte is prefixed to the encoded data.
|
293
|
+
|
294
|
+
**Default** is `UTF-16`
|
295
|
+
|
296
|
+
##### default_decode_dest
|
297
|
+
This is more or less only for internal use and doesn't affect the written tag. This encoding is used to decode the tag if an encoding
|
298
|
+
is applied and later on, when the tag is written the `default_encode_dest` is used again.
|
299
|
+
|
300
|
+
**Default** is `UTF-8`
|
301
|
+
|
302
|
+
##### padding_bytes
|
303
|
+
After the last Frame is written, optionally padding null bytes can be written. Padding bytes can for example be used to add additionally information,
|
304
|
+
e.g. an `ID3 Footer` without rewriting the whole tag.
|
305
|
+
|
306
|
+
**Default** is `20`
|
307
|
+
|
308
|
+
##### ignore_v23_frame_error
|
309
|
+
|
310
|
+
Some Frames are available for ID3v2.4 but not for ID3v2.3, for example the Sort Order frames like TSOT.
|
311
|
+
But some application still interpret them correctly, hence they can be used in ID3v2.3 even if they are not
|
312
|
+
in the specifications. When this flag is true, no error will be raised if an invalid Frame should be added to the Tag.
|
313
|
+
In those cases it is for example possible to add an ID3v2.4 only Tag to an ID3v2.3 Tag.
|
314
|
+
|
315
|
+
**Default** is `true`
|
316
|
+
|
317
|
+
##### ignore_v24_frame_error
|
318
|
+
|
319
|
+
Same as `ignore_v23_frame_error` just for ID3v2.4 Tag.
|
320
|
+
|
321
|
+
**Default** is `true`
|
322
|
+
|
323
|
+
##### add_size_frame
|
324
|
+
|
325
|
+
ID3v2.3 has a size (TSIZ) frame that contains the file size in bytes without ID3v2 Tag size. When this flag is true
|
326
|
+
this Frame will be added automatically if it is not present.
|
327
|
+
|
328
|
+
Note: In ID3v2.4 this Frame was removed. So in order to use this flag for ID3v2.4 Tags, the flag `ignore_v24_frame_error`
|
329
|
+
must be `true` too.
|
330
|
+
|
331
|
+
**Default** is `false`
|
332
|
+
|
333
|
+
#### How to set Options
|
334
|
+
|
335
|
+
Options can be set on a global level, then those options will be applied to all Audio Files that are created,
|
336
|
+
or the option is only applied to one Audio File.
|
337
|
+
|
338
|
+
To define global options, simply create an `Id3Taginator` instance and apply the options:
|
339
|
+
```ruby
|
340
|
+
id3taginator = Id3Taginator.global_options
|
341
|
+
.default_encode_for_destination(Encoding::UTF_16)
|
342
|
+
.default_decode_for_destination(Encoding::UTF_8)
|
343
|
+
.tag_padding(42)
|
344
|
+
.ignore_v23_frame_error(true)
|
345
|
+
.ignore_v24_frame_error(false)
|
346
|
+
.add_size_frame(true)
|
347
|
+
|
348
|
+
audio_file = id3taginator.build_by_path('path/to/file')
|
349
|
+
# => options are applied
|
350
|
+
another_audio_file = id3taginator.build_by_path('path/to/another/file')
|
351
|
+
# => options are applied too
|
352
|
+
```
|
353
|
+
if options should be applied to one audio file, create the file and apply the options directly to this file
|
354
|
+
```ruby
|
355
|
+
audio_file = Id3Taginator.build_by_path('path/to/file')
|
356
|
+
audio_file.default_encode_for_destination(Encoding::UTF_16)
|
357
|
+
.default_decode_for_destination(Encoding::UTF_8)
|
358
|
+
.tag_padding(42)
|
359
|
+
.ignore_v23_frame_error(true)
|
360
|
+
.ignore_v24_frame_error(false)
|
361
|
+
.add_size_frame(true)
|
362
|
+
```
|
363
|
+
Here, the options are only applied to this Audio File instance. All other created instance use the default options.
|
364
|
+
|
365
|
+
### Create Entities
|
366
|
+
|
367
|
+
Some Setter such as `copyright=` or `add_picture` require an Entity Instance as an argument. All arguments can be created
|
368
|
+
like this
|
369
|
+
|
370
|
+
```ruby
|
371
|
+
Id3Taginator.create_buffer(42, false, 5)
|
372
|
+
Id3Taginator.create_picture_from_data('image/png', :COVER_FRONT, 'Description', 'Some picture data')
|
373
|
+
```
|
374
|
+
There is a create method for all Entities.
|
375
|
+
|
376
|
+
### Custom Frames
|
377
|
+
|
378
|
+
If a frame is currently not implemented, but is required, it can still be added or modified using Custom Frames. If the Frame is already
|
379
|
+
present in the ID3v2 Tag, then it will be available as a Custom Frame. Custom Frames can be added and modified as all other Frames
|
380
|
+
```ruby
|
381
|
+
# CSTM is the frame id of the frame we want to fetch
|
382
|
+
my_custom_frame = v2_tag.custom_frame('CSTM')
|
383
|
+
frame_content = my_custom_frame.content
|
384
|
+
# modify the content
|
385
|
+
v2_tag.add_custom_frame('CSTM', frame_content)
|
386
|
+
|
387
|
+
v2_tag.add_custom_frame('ABCD', 'my new frame content')
|
388
|
+
```
|
389
|
+
Here, the old content is replaced by the new content, and a new custom Frame is added.
|
390
|
+
If multiple custom frames with the same Frame ID should exist, an optional `selector_lambda` can be passed as an argument.
|
391
|
+
|
392
|
+
## Development
|
393
|
+
|
394
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
395
|
+
|
396
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
397
|
+
|
398
|
+
## Contributing
|
399
|
+
|
400
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cfe86/id3taginator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/cfe86/id3taginator/blob/master/CODE_OF_CONDUCT.md).
|
401
|
+
|
402
|
+
## License
|
403
|
+
|
404
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
405
|
+
|
406
|
+
## Code of Conduct
|
407
|
+
|
408
|
+
Everyone interacting in the Id3taginator project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/cfe86/id3taginator/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'id3taginator'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'id3taginator'
|
5
|
+
spec.version = '0.8'
|
6
|
+
spec.authors = ['Christian Feier']
|
7
|
+
spec.email = ['Christian.Feier@gmail.com']
|
8
|
+
|
9
|
+
spec.summary = 'Id3Taginator is a ID3v1, ID3v2.2/3/4 tag reader and writer fully written in Ruby and does not'\
|
10
|
+
'rely on TagLib or any other 3rd party library to read/write id3tags. It aims to offer a simple'\
|
11
|
+
'way to read and write ID3Tags.'
|
12
|
+
spec.description = 'Id3Taginator is a ID3v1, ID3v2.2/3/4 tag reader and writer fully written in Ruby and does not'\
|
13
|
+
'rely on TagLib or any other 3rd party library to read/write id3tags. It aims to offer a simple'\
|
14
|
+
'way to read and write ID3Tags.'
|
15
|
+
spec.homepage = 'https://github.com/cfe86/Id3Taginator'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
spec.required_ruby_version = '>= 2.5.1'
|
18
|
+
|
19
|
+
spec.metadata['homepage_uri'] = 'https://github.com/cfe86/Id3Taginator'
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/cfe86/Id3Taginator'
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/cfe86/Id3Taginator'
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
27
|
+
end
|
28
|
+
spec.bindir = 'exe'
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ['lib']
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, checkout our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
class AudioFile
|
5
|
+
include Extensions::Optionable
|
6
|
+
|
7
|
+
attr_reader :id3v1_tag, :id3v2_tag
|
8
|
+
|
9
|
+
# constructor
|
10
|
+
#
|
11
|
+
# @param file [StringIO, IO, File] the file stream
|
12
|
+
# @param options [Options::Options] the options to use for this file
|
13
|
+
# @param no_tag_parsing [Boolean] if true, no tag parsing is done (only use this for testing purposes, otherwise it
|
14
|
+
# renders the file literally useless)
|
15
|
+
def initialize(file, options = Options::Options.new, no_tag_parsing: false)
|
16
|
+
@file = file
|
17
|
+
@options = options
|
18
|
+
|
19
|
+
parse_id3v2_tag unless no_tag_parsing
|
20
|
+
parse_id3v1_tag unless no_tag_parsing
|
21
|
+
read_audio_data unless no_tag_parsing
|
22
|
+
end
|
23
|
+
|
24
|
+
# parses the id3v1 tag
|
25
|
+
def parse_id3v1_tag
|
26
|
+
length = @file.is_a?(File) ? @file.size : @file.length
|
27
|
+
if length > 128
|
28
|
+
@file.seek(-128, IO::SEEK_END)
|
29
|
+
@id3v1_tag = Id3v1Tag.build_from_file(@file) if Id3v1Tag.id3v1_tag?(@file)
|
30
|
+
end
|
31
|
+
|
32
|
+
@audio_end_index = @id3v1_tag.nil? ? @file.size : @file.size - Id3v1Tag::TAG_SIZE
|
33
|
+
end
|
34
|
+
|
35
|
+
# parses the id3v2 tag
|
36
|
+
def parse_id3v2_tag
|
37
|
+
@id3v2_tag = Id3v2Tag.build_from_file(@file, @options) if Id3v2Tag.id3v2_tag?(@file)
|
38
|
+
@audio_start_index = @id3v2_tag.nil? ? 0 : @id3v2_tag.total_tag_size
|
39
|
+
|
40
|
+
@id3v2_tag&.add_size_tag_if_not_present(audio_size_without_id3v2_tag)
|
41
|
+
end
|
42
|
+
|
43
|
+
# removes the id3v1 tag
|
44
|
+
def remove_id3v1_tag
|
45
|
+
@id3v1_tag = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# creates the id3v1 tag. If a tag already exists, it must be removed first.
|
49
|
+
#
|
50
|
+
# @return [Id3v1Tag] the created Id3v1 tag
|
51
|
+
def create_id3v1_tag
|
52
|
+
raise Errors::Id3TagError, 'An ID3v1 tag already exists. Can\'t create a 2nd one.' unless @id3v1_tag.nil?
|
53
|
+
|
54
|
+
@id3v1_tag = Id3v1Tag.new
|
55
|
+
@id3v1_tag
|
56
|
+
end
|
57
|
+
|
58
|
+
# removes the id3v1 tag
|
59
|
+
def remove_id3v2_tag
|
60
|
+
@id3v2_tag = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# creates the id3v2.2 tag. If a tag already exists, it must be removed first.
|
64
|
+
#
|
65
|
+
# @return [Id3v22Tag, Id3v23Tag, Id3v24Tag] the created Id3v2.2 tag
|
66
|
+
def create_id3v2_2_tag
|
67
|
+
raise Errors::Id3TagError, 'An ID3v2.2 tag already exists. Can\'t create a 2nd one.' unless @id3v1_tag.nil?
|
68
|
+
|
69
|
+
@id3v2_tag = Id3v2Tag.build_for_version(2, @options)
|
70
|
+
@id3v2_tag
|
71
|
+
end
|
72
|
+
|
73
|
+
# creates the id3v2.3 tag. If a tag already exists, it must be removed first.
|
74
|
+
#
|
75
|
+
# @return [Id3v22Tag, Id3v23Tag, Id3v24Tag] the created Id3v2.3 tag
|
76
|
+
def create_id3v2_3_tag
|
77
|
+
raise Errors::Id3TagError, 'An ID3v2.3 tag already exists. Can\'t create a 2nd one.' unless @id3v1_tag.nil?
|
78
|
+
|
79
|
+
@id3v2_tag = Id3v2Tag.build_for_version(3, @options)
|
80
|
+
@id3v2_tag
|
81
|
+
end
|
82
|
+
|
83
|
+
# creates the id3v2.4 tag. If a tag already exists, it must be removed first.
|
84
|
+
#
|
85
|
+
# @return [Id3v22Tag, Id3v23Tag, Id3v24Tag] the created Id3v2.4 tag
|
86
|
+
def create_id3v2_4_tag
|
87
|
+
raise Errors::Id3TagError, 'An ID3v2.4 tag already exists. Can\'t create a 2nd one.' unless @id3v1_tag.nil?
|
88
|
+
|
89
|
+
@id3v2_tag = Id3v2Tag.build_for_version(4, @options)
|
90
|
+
@id3v2_tag
|
91
|
+
end
|
92
|
+
|
93
|
+
# writes the audio file to the specified path
|
94
|
+
# Note: This path can be the same path as the path that was used to read the file
|
95
|
+
#
|
96
|
+
# @param path [String] the file where to write the modified file too
|
97
|
+
def write_audio_file(path)
|
98
|
+
out_file = File.open(path, 'w')
|
99
|
+
out_file.write(audio_file_to_bytes)
|
100
|
+
end
|
101
|
+
|
102
|
+
# creates a byte array represented as a String (str.bytes) of the modified file data
|
103
|
+
#
|
104
|
+
# @return [String] the byte array of the file represented as a String
|
105
|
+
def audio_file_to_bytes
|
106
|
+
@id3v2_tag&.add_size_tag_if_not_present(audio_size_without_id3v2_tag)
|
107
|
+
|
108
|
+
bytes = []
|
109
|
+
bytes << @id3v2_tag.to_bytes unless @id3v2_tag.nil?
|
110
|
+
bytes << read_audio_data
|
111
|
+
bytes << @id3v1_tag.to_bytes unless @id3v1_tag.nil?
|
112
|
+
|
113
|
+
bytes.inject([]) { |sum, x| sum + x.bytes }.pack('C*')
|
114
|
+
end
|
115
|
+
|
116
|
+
# reads the audio data part from the file
|
117
|
+
#
|
118
|
+
# @return [String] the byte array audio data part of the file represented as a String (str.bytes)
|
119
|
+
def read_audio_data
|
120
|
+
return @audio_data unless @audio_data.nil?
|
121
|
+
|
122
|
+
@file.seek(@audio_start_index)
|
123
|
+
@audio_data = @file.read(@audio_end_index - @audio_start_index)
|
124
|
+
@audio_data
|
125
|
+
end
|
126
|
+
|
127
|
+
# returns the audio size without the id3v2 tag (note, if an id3v1 header is present, this size is included)
|
128
|
+
#
|
129
|
+
# @return [Integer] the size of the audio file without the id3v2 tag size
|
130
|
+
def audio_size_without_id3v2_tag
|
131
|
+
@audio_start_index - @file.size
|
132
|
+
end
|
133
|
+
|
134
|
+
private :parse_id3v2_tag, :parse_id3v1_tag, :audio_size_without_id3v2_tag
|
135
|
+
end
|
136
|
+
end
|