id3taginator 0.8

Sign up to get free protection for your applications and to get access to all the features.
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