-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathid3picture.c
245 lines (222 loc) · 7.11 KB
/
id3picture.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#include "id3picture.h"
// Global variables because I don't to put all these in a struct... maybe later.
long tagStart;
long picFrameStart;
long prevSize;
long fileTailLen;
long picFrameSize;
long prevFramesLen;
int version; //ID3v2.x where x should be 3 or 4
int main(int argc, char * argv[]) {
FileChunks * chunks;
const char * audioFilename = argv[1];
const char * imageFilename = argv[2];
char * picFrame;
version = getVersion(audioFilename);
picFrame = constructPicFrame(imageFilename);
chunks = readInFile(audioFilename);
writeOutFile(audioFilename, chunks, picFrame);
return 0;
}
/**
* Pass audio file name. Function will return file chunks:
* pre-tag buffer, header, previous frames, and file tail.
* The header is for the output file (updated tag size).
*/
FileChunks * readInFile(const char * audioFilename) {
FILE * file = fopen(audioFilename, "rb");
char prevHeader[10];
FileChunks * chunks = malloc(sizeof(FileChunks));
long prevSize;
if (file == NULL) {
printf("Audio file not opened successfully. Exiting.\n");
exit(1);
}
chunks->preTagBuf = malloc(tagStart);
fread(chunks->preTagBuf, 1, tagStart, file);
fread(prevHeader, 1, 10, file);
chunks->header = updateID3TagHeader(prevHeader);
picFrameStart = skipFrames(file);
fseek(file, 0, SEEK_END);
chunks->fileTail = malloc(fileTailLen = (ftell(file) - picFrameStart));
fseek(file, picFrameStart, SEEK_SET);
fread(chunks->fileTail, 1, fileTailLen, file);
chunks->prevFrames = malloc(prevFramesLen = (picFrameStart - (tagStart + 10)));
fseek(file, tagStart + 10, SEEK_SET);
fread(chunks->prevFrames, 1, prevFramesLen, file);
fclose(file);
return chunks;
}
/**
* Pass audio file name, file chunks, and the pic frame. The new file
* will be written whose name is the input filename appended with '_out'
* (only works for mp3's right now because it's hardcoded sorry).
*/
void writeOutFile(const char * audioFilename, FileChunks * chunks, char * picFrame) {
FILE * file;
char outFilename[strlen(audioFilename) + 5];
strcpy(outFilename, audioFilename);
strcpy(strstr(outFilename, ".mp3"), "_out.mp3");
printf("Output file name : %s\n", outFilename);
file = fopen(outFilename, "wb+");
if (file == NULL) {
printf("New audio file not created successfully. Exiting.\n");
exit(1);
}
fwrite(chunks->preTagBuf, 1, tagStart, file);
fwrite(chunks->header, 1, 10, file);
fwrite(chunks->prevFrames, 1, prevFramesLen, file);
fwrite(picFrame, 1, picFrameSize, file);
fwrite(chunks->fileTail, 1, fileTailLen, file);
fclose(file);
printf("Successfully wrote output file with picture.\n");
}
/**
* Pass an open file with a tag to search for. Returns true if file
* contains tag, and the file will point at the position where the
* tag begins, else return false.
*/
int fileContains(FILE * file, const char * tag) {
int i, data, tagLen = strlen(tag);
while ((data) != EOF) {
for (i = 0; i < tagLen; i++) {
if ((data = fgetc(file)) != tag[i]) { // Not part of tag
fseek(file, -i, SEEK_CUR);
break;
} else if (i + 1 == tagLen) { // Found complete tag
fseek(file, -strlen(tag), SEEK_CUR);
return 1;
}
}
}
return 0;
}
/**
* Pass a filename of a jpeg or png, and a buffer will be allocated
* that contains the APIC ID3 frame, and frameSize will be set to the
* size of the frame plus ten bytes for the frame header.
*/
char * constructPicFrame(const char * imageFilename) {
int jpg;
long picSize, frameSize;
unsigned char * picBuffer, * frame;
unsigned char frameHeader[10] = "APIC\0\0\0\0\0\0";
FILE * picFile = fopen(imageFilename, "rb");;
fseek(picFile, 0, SEEK_END);
picSize = ftell(picFile);
rewind(picFile);
picBuffer = malloc(picSize);
fread(picBuffer, 1, picSize, picFile);
fclose(picFile);
jpg = picIsJpg(imageFilename);
frameSize = picSize + 13 + jpg;
if (version == 4) {
frameHeader[4] = (frameSize >> 21) & 0x7F;
frameHeader[5] = (frameSize >> 14) & 0x7F;
frameHeader[6] = (frameSize >> 7) & 0x7F;
frameHeader[7] = (frameSize) & 0x7F;
}
else {
frameHeader[4] = (frameSize >> 24);
frameHeader[5] = (frameSize >> 16);
frameHeader[6] = (frameSize >> 8);
frameHeader[7] = (frameSize);
}
frame = malloc(frameSize);
memcpy(frame, frameHeader, sizeof(frameHeader));
memcpy(frame + sizeof(frameHeader), jpg ? "\0image/jpeg\0\3\0" : "\0image/png\0\3\0", 13 + jpg);
memcpy(frame + sizeof(frameHeader) + 13 + jpg, picBuffer, picSize);
picFrameSize = frameSize + 10;
printf("Pic frame size %lu\n", picFrameSize);
return frame;
}
/**
* Pass the previous ID3 Tag header and size of new pic frame, and
* function returns the updated tag header. It will fail if the version,
* is less than ID3v2.3, and the global version will be set to 3 or 4
* (v4 uses sync-safe ints in frame size, v3 doesn't).
*/
char * updateID3TagHeader(const char * prevHeader) {
long size;
char * header = malloc(10);
int i;
memcpy(header, prevHeader, 10);
if (version < 3) exit(1);
prevSize = (header[6] << 21) +
(header[7] << 14) +
(header[8] << 7) +
(header[9]);
size = prevSize + picFrameSize;
header[6] = (size >> 21) & 0x7F;
header[7] = (size >> 14) & 0x7F;
header[8] = (size >> 7) & 0x7F;
header[9] = (size) & 0x7F;
return header;
}
/**
* Returns true if filename that of a jpeg file, else returns false.
*/
int picIsJpg(const char * imageFilename) {
int i;
char lower[strlen(imageFilename)];
strcpy(lower, imageFilename);
for (i = 0; lower[i]; i++) lower[i] = tolower(lower[i]);
return (strstr(lower, "jpg") || strstr(lower, "jpeg")) ? 1 : 0;
}
/**
* Pass the opened file position at the start of the first frame of the tag.
* Scan through each of the existing frames by reading each frame header then skipping it
* until we either get to the zero-padding of the tag or we get to the MP3 audio frames.
* Returns the position of the end of the last frame of the tag, which the file pointer
* will still be positioned at.
*/
long skipFrames(FILE * file) {
char header[10];
long size;
while (1) {
fread(header, 1, 10, file);
if ((header[0] | header[1] | header[2] | header[3]) && ((ftell(file) - 10 - tagStart) < prevSize)) {
if (version == 4) {
size = (header[4] << 21) +
(header[5] << 14) +
(header[6] << 7) +
(header[7]);
} else {
size = (header[4] << 24) +
(header[5] << 16) +
(header[6] << 8) +
(header[7]);
}
fseek(file, size, SEEK_CUR);
} else {
fseek(file, -10, SEEK_CUR);
printf("Final Position of existing ID3 Tag Frames: %lu\n", ftell(file));
return ftell(file);
}
}
return 0;
}
/**
* Checks that an ID3 tag exists and returns the version of the ID3 tag,
* else it will exit with 1 if file didn't open or exit with 0 if no tag exists.
*/
int getVersion(const char * audioFilename) {
FILE * file = fopen(audioFilename, "rb");
int v;
if (file == NULL) {
printf("Audio file not opened successfully. Exiting.\n");
exit(1);
}
if (fileContains(file, "ID3")) {
printf("Found ID3 Tag. Position %lu\n", tagStart = ftell(file));
fseek(file, strlen("ID3"), SEEK_CUR);
v = fgetc(file);
fclose(file);
return v;
} else {
fclose(file);
printf("No ID3 Tag found. Ensure your file has an existing ID3v2.3 or v2.4 tag.\n");
exit(0);
}
return -1;
}