id3taginator 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.idea/.gitignore +8 -0
  4. data/.idea/ID3Taginator.iml +73 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.rspec +3 -0
  9. data/.rubocop.yml +26 -0
  10. data/CHANGELOG.md +5 -0
  11. data/CODE_OF_CONDUCT.md +84 -0
  12. data/Gemfile +12 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +408 -0
  15. data/Rakefile +12 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/id3taginator.gemspec +37 -0
  19. data/lib/id3taginator/audio_file.rb +136 -0
  20. data/lib/id3taginator/errors/id3_tag_error.rb +12 -0
  21. data/lib/id3taginator/extensions/argument_check.rb +88 -0
  22. data/lib/id3taginator/extensions/comparable.rb +28 -0
  23. data/lib/id3taginator/extensions/encodable.rb +215 -0
  24. data/lib/id3taginator/extensions/optionable.rb +73 -0
  25. data/lib/id3taginator/frames/buffer/entities/buffer.rb +26 -0
  26. data/lib/id3taginator/frames/buffer/rbuf_recommended_buffer_size_frame.rb +59 -0
  27. data/lib/id3taginator/frames/buffer_frames.rb +32 -0
  28. data/lib/id3taginator/frames/comment/comm_comment_frame.rb +56 -0
  29. data/lib/id3taginator/frames/comment/entities/comment.rb +26 -0
  30. data/lib/id3taginator/frames/comment_frames.rb +42 -0
  31. data/lib/id3taginator/frames/count/entities/popularimeter.rb +26 -0
  32. data/lib/id3taginator/frames/count/pcnt_play_counter_frame.rb +40 -0
  33. data/lib/id3taginator/frames/count/popm_popularimeter_frame.rb +52 -0
  34. data/lib/id3taginator/frames/count_frames.rb +51 -0
  35. data/lib/id3taginator/frames/custom_frame.rb +39 -0
  36. data/lib/id3taginator/frames/custom_frames.rb +54 -0
  37. data/lib/id3taginator/frames/encryption/aenc_audio_encryption.rb +54 -0
  38. data/lib/id3taginator/frames/encryption/encr_encryption_method_frame.rb +49 -0
  39. data/lib/id3taginator/frames/encryption/entities/audio_encryption.rb +28 -0
  40. data/lib/id3taginator/frames/encryption/entities/encryption_method.rb +26 -0
  41. data/lib/id3taginator/frames/encryption_frames.rb +77 -0
  42. data/lib/id3taginator/frames/frameable.rb +75 -0
  43. data/lib/id3taginator/frames/geo/entities/encapsulated_object.rb +28 -0
  44. data/lib/id3taginator/frames/geo/geob_general_encapsulated_object_frame.rb +59 -0
  45. data/lib/id3taginator/frames/geo_frames.rb +41 -0
  46. data/lib/id3taginator/frames/grouping/entities/group_identification.rb +26 -0
  47. data/lib/id3taginator/frames/grouping/grid_group_identification_frame.rb +49 -0
  48. data/lib/id3taginator/frames/grouping/grp1_grouping_frame.rb +40 -0
  49. data/lib/id3taginator/frames/grouping_frames.rb +61 -0
  50. data/lib/id3taginator/frames/has_id.rb +51 -0
  51. data/lib/id3taginator/frames/id3v23_frame_flags.rb +64 -0
  52. data/lib/id3taginator/frames/id3v24_frame_flags.rb +80 -0
  53. data/lib/id3taginator/frames/id3v2_frame.rb +249 -0
  54. data/lib/id3taginator/frames/id3v2_frame_factory.rb +98 -0
  55. data/lib/id3taginator/frames/ipl/entities/involved_person.rb +24 -0
  56. data/lib/id3taginator/frames/ipl/ipls_involved_people_frame.rb +48 -0
  57. data/lib/id3taginator/frames/ipl_frames.rb +31 -0
  58. data/lib/id3taginator/frames/lyrics/entities/unsync_lyrics.rb +26 -0
  59. data/lib/id3taginator/frames/lyrics/uslt_unsync_lyrics_frame.rb +56 -0
  60. data/lib/id3taginator/frames/lyrics_frames.rb +42 -0
  61. data/lib/id3taginator/frames/mcdi/mcdi_music_cd_identifier_frame.rb +40 -0
  62. data/lib/id3taginator/frames/mcdi_frames.rb +28 -0
  63. data/lib/id3taginator/frames/picture/apic_picture_frame.rb +106 -0
  64. data/lib/id3taginator/frames/picture/entities/picture.rb +32 -0
  65. data/lib/id3taginator/frames/picture_frames.rb +51 -0
  66. data/lib/id3taginator/frames/private/entities/private_frame.rb +24 -0
  67. data/lib/id3taginator/frames/private/priv_private_frame.rb +45 -0
  68. data/lib/id3taginator/frames/private_frames.rb +40 -0
  69. data/lib/id3taginator/frames/text/entities/copyright.rb +24 -0
  70. data/lib/id3taginator/frames/text/entities/date.rb +24 -0
  71. data/lib/id3taginator/frames/text/entities/part_of_set.rb +24 -0
  72. data/lib/id3taginator/frames/text/entities/time.rb +24 -0
  73. data/lib/id3taginator/frames/text/entities/track_number.rb +24 -0
  74. data/lib/id3taginator/frames/text/entities/user_info.rb +24 -0
  75. data/lib/id3taginator/frames/text/talb_album_frame.rb +40 -0
  76. data/lib/id3taginator/frames/text/tbpm_bpm_frame.rb +40 -0
  77. data/lib/id3taginator/frames/text/tcom_composer_frame.rb +42 -0
  78. data/lib/id3taginator/frames/text/tcon_genre_frame.rb +104 -0
  79. data/lib/id3taginator/frames/text/tcop_copyright_frame.rb +55 -0
  80. data/lib/id3taginator/frames/text/tdat_date_frame.rb +60 -0
  81. data/lib/id3taginator/frames/text/tdly_playlist_delay_frame.rb +40 -0
  82. data/lib/id3taginator/frames/text/tenc_encoded_by_frame.rb +40 -0
  83. data/lib/id3taginator/frames/text/text_writers_frame.rb +43 -0
  84. data/lib/id3taginator/frames/text/tflt_file_type_frame.rb +71 -0
  85. data/lib/id3taginator/frames/text/time_time_frame.rb +60 -0
  86. data/lib/id3taginator/frames/text/tit1_content_group_description_frame.rb +40 -0
  87. data/lib/id3taginator/frames/text/tit2_title_frame.rb +40 -0
  88. data/lib/id3taginator/frames/text/tit3_subtitle_frame.rb +40 -0
  89. data/lib/id3taginator/frames/text/tkey_initial_key_frame.rb +40 -0
  90. data/lib/id3taginator/frames/text/tlan_language_frame.rb +48 -0
  91. data/lib/id3taginator/frames/text/tlen_length_frame.rb +40 -0
  92. data/lib/id3taginator/frames/text/tmed_media_type_frame.rb +40 -0
  93. data/lib/id3taginator/frames/text/toal_original_album_frame.rb +40 -0
  94. data/lib/id3taginator/frames/text/tofn_original_filename_frame.rb +40 -0
  95. data/lib/id3taginator/frames/text/toly_original_writers_frame.rb +43 -0
  96. data/lib/id3taginator/frames/text/tope_original_artists_frame.rb +43 -0
  97. data/lib/id3taginator/frames/text/tory_original_release_year_frame.rb +42 -0
  98. data/lib/id3taginator/frames/text/town_file_owner_frame.rb +40 -0
  99. data/lib/id3taginator/frames/text/tpe1_artist_frame.rb +43 -0
  100. data/lib/id3taginator/frames/text/tpe2_album_artist_frame.rb +40 -0
  101. data/lib/id3taginator/frames/text/tpe3_conductor_frame.rb +40 -0
  102. data/lib/id3taginator/frames/text/tpe4_modified_by_frame.rb +40 -0
  103. data/lib/id3taginator/frames/text/tpos_part_of_set_frame.rb +50 -0
  104. data/lib/id3taginator/frames/text/tpub_publisher_frame.rb +40 -0
  105. data/lib/id3taginator/frames/text/trck_track_number_frame.rb +50 -0
  106. data/lib/id3taginator/frames/text/trda_recording_dates_frame.rb +42 -0
  107. data/lib/id3taginator/frames/text/trsn_internet_radio_station_frame.rb +40 -0
  108. data/lib/id3taginator/frames/text/tsiz_size_frame.rb +41 -0
  109. data/lib/id3taginator/frames/text/tsoa_album_sort_order_frame.rb +40 -0
  110. data/lib/id3taginator/frames/text/tsop_performer_sort_order_frame.rb +40 -0
  111. data/lib/id3taginator/frames/text/tsot_title_sort_order_frame.rb +40 -0
  112. data/lib/id3taginator/frames/text/tsrc_isrc_frame.rb +40 -0
  113. data/lib/id3taginator/frames/text/tsse_encoder_frame.rb +40 -0
  114. data/lib/id3taginator/frames/text/txxx_user_text_info_frame.rb +51 -0
  115. data/lib/id3taginator/frames/text/tyer_year_frame.rb +42 -0
  116. data/lib/id3taginator/frames/text_frames.rb +840 -0
  117. data/lib/id3taginator/frames/tos/entities/ownership.rb +26 -0
  118. data/lib/id3taginator/frames/tos/entities/terms_of_use.rb +24 -0
  119. data/lib/id3taginator/frames/tos/owne_ownership_frame.rb +53 -0
  120. data/lib/id3taginator/frames/tos/user_terms_of_use_frame.rb +49 -0
  121. data/lib/id3taginator/frames/tos_frames.rb +54 -0
  122. data/lib/id3taginator/frames/ufid/entities/ufid_info.rb +24 -0
  123. data/lib/id3taginator/frames/ufid/ufid_unique_file_identifier_frame.rb +47 -0
  124. data/lib/id3taginator/frames/ufid_frames.rb +40 -0
  125. data/lib/id3taginator/frames/url/entities/user_info.rb +24 -0
  126. data/lib/id3taginator/frames/url/wcom_commercial_url_frame.rb +40 -0
  127. data/lib/id3taginator/frames/url/wcop_copyright_url_frame.rb +40 -0
  128. data/lib/id3taginator/frames/url/woaf_official_file_webpage_frame.rb +40 -0
  129. data/lib/id3taginator/frames/url/woar_official_artist_webpage_frame.rb +40 -0
  130. data/lib/id3taginator/frames/url/woas_official_source_webpage_frame.rb +40 -0
  131. data/lib/id3taginator/frames/url/wors_official_radio_station_homepage_frame.rb +40 -0
  132. data/lib/id3taginator/frames/url/wpay_payment_url_frame.rb +40 -0
  133. data/lib/id3taginator/frames/url/wpub_official_publisher_webpage_frame.rb +40 -0
  134. data/lib/id3taginator/frames/url/wxxx_user_url_link_frame.rb +50 -0
  135. data/lib/id3taginator/frames/url_frames.rb +195 -0
  136. data/lib/id3taginator/genres.rb +168 -0
  137. data/lib/id3taginator/header/id3v23_extended_header.rb +37 -0
  138. data/lib/id3taginator/header/id3v24_extended_header.rb +100 -0
  139. data/lib/id3taginator/header/id3v2_flags.rb +64 -0
  140. data/lib/id3taginator/id3v1_tag.rb +156 -0
  141. data/lib/id3taginator/id3v22_tag.rb +30 -0
  142. data/lib/id3taginator/id3v23_tag.rb +63 -0
  143. data/lib/id3taginator/id3v24_tag.rb +75 -0
  144. data/lib/id3taginator/id3v2_tag.rb +241 -0
  145. data/lib/id3taginator/options/options.rb +33 -0
  146. data/lib/id3taginator/util/compress_util.rb +25 -0
  147. data/lib/id3taginator/util/math_util.rb +68 -0
  148. data/lib/id3taginator/util/sync_util.rb +65 -0
  149. data/lib/id3taginator.rb +449 -0
  150. 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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Errors
5
+ class Id3TagError < StandardError
6
+
7
+ def initialize(msg = nil)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end