1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 //apparantly some systems, such as cygwin, do not have getopt defined in unistd
5 #ifdef HAVE_GETOPT_H
6 #include <getopt.h>
7 #endif
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #ifdef HAVE_LIBREADLINE
13 #ifdef OLD_READLINE
14 extern "C" {
15 #endif
16 #include <readline/readline.h>
17 #include <readline/history.h>
18 #ifdef OLD_READLINE
19 }
20 #endif
21 #ifdef HAVE_LIBTERMCAP
22 #include <termcap.h>
23 #endif
24 #endif //HAVE_LIBREADLINE
25 #ifdef HAVE_LIBCURSES
26 #include <curses.h>
27 //stupid curses macros.
28 #undef erase
29 #undef clear
30 #endif
31 #include "misc.h"
32 
33 /* Field Length offsets
34  *  Tag 3 0-2
35  *  Songname 30 3-32
36  *  Artist 30 33-62
37  *  Album 30 63-92
38  *  Year 4 93-96
39  *  Comment 30 97-126
40  *  or{
41  *    Comment 28 97-124
42  *    zero 1 125
43  *    Tracknum 1 126
44  *  }
45  *  Genre 1 127
46  */
47 struct s_v11sub {
48 	char comment[28];
49 	unsigned char empty;
50 	unsigned char tracknum;
51 };
52 struct s_id3{
53 	char tag[3];
54 	char songname[30];
55 	char artist[30];
56 	char album[30];
57 	char year[4];
58 	union{
59 		char comment[30];
60 		s_v11sub v11;
61 	};
62 	//char comment[28];
63 	//unsigned char empty;
64 	//unsigned char tracknum;
65 	unsigned char genre;
66 }id3;
67 
68 #define NUM_GENRE 148
69 
70 #define QUIET_PROGRESS	(1 << 0) /* 'File ...' output */
71 #define QUIET_SONG	(1 << 1) /* s */
72 #define QUIET_ARTIST	(1 << 2) /* n */
73 #define QUIET_ALBUM	(1 << 3) /* a */
74 #define QUIET_YEAR	(1 << 4) /* y */
75 #define QUIET_COMMENT	(1 << 5) /* c */
76 #define QUIET_GENRE	(1 << 6) /* g */
77 #define QUIET_TRACK	(1 << 7) /* k */
78 
79 
80 const char *genre_list[NUM_GENRE+1]={
81 	"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk",
82 	"Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies",
83 	"Other", "Pop", "R&B", "Rap", "Reggae", "Rock",
84 	"Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks",
85 	"Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk",
86 	"Fusion", "Trance", "Classical", "Instrumental", "Acid", "House",
87 	"Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass",
88 	"Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
89 	"Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
90 	"Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
91 	"Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
92 	"New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
93 	"Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical",
94 	"Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing",
95 	"Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
96 	"Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
97 	"Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
98 	"Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
99 	"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
100 	"Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet",
101 	"Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
102 	"Goa", "Drum & Bass", "Club House", "Hardcore", "Terror",
103 	"Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat",
104 	"Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
105 	"Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
106 	"SynthPop",
107 	NULL
108 };
109 
110 void i3info(const char *file,int quiet);
111 void i3remove(const char *file,int quiet);
112 void i3rename(const char *file,int quiet,int test);
113 void i3edit(const char * file, const char * defsongname,
114 		const char * defartist, const char * defalbum,
115 		const char * defyear, const char * defcomment,
116 		const char * deftracknum, const char * defgenre,int quiet);
117 void stredit(const char * name, int maxlen, char * buf);
118 void print_genre_list(int mode);
119 
version()120 void version()
121 {
122 	printf("id3ed v1.10.4 - mpeg layer 3 file information editor\n");
123 }
124 
license()125 void license()
126 {
127 	printf("Copyright 1998-2001,2003 Matthew Mueller <donut@azstarnet.com>\n");
128 	printf("\n\
129 This program is free software; you can redistribute it and/or modify\n\
130 it under the terms of the GNU General Public License as published by\n\
131 the Free Software Foundation; either version 2 of the License, or\n\
132 (at your option) any later version.\n\n\
133 This program is distributed in the hope that it will be useful,\n\
134 but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
135 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
136 GNU General Public License for more details.\n\n\
137 You should have received a copy of the GNU General Public License\n\
138 along with this program; if not, write to the Free Software\n\
139 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n");
140 }
141 
print_help(void)142 void print_help(void){
143 	version();
144 	printf("\
145 Usage: id3ed [-s songname] [-n artist] [-a album] [-y year] [-c comment]\n\
146 	     [-k tracknum] [-g genre] [-q] [-SNAYCKG] [-l/-L] [-r]\n\
147 	     [-i] <mp3files> [-v]\n\n\
148   -q			no line interface; only set tags specified on command\n\
149   			line. Use twice to suppress all output except errors.\n\
150   -SNAYCKG		prompt to edit the specified tags only\n\
151   			Other tags can still be set with the -[snaycg] options.\n\
152   -l/-L			display list of genres\n\
153   -r			remove id3 tag from files\n\
154   -i			show current id3 tag only, don't edit\n\
155   -v			output program version/license\n");
156 }
157 
main(int argc,char ** argv)158 int main(int argc,char ** argv){
159 	int c,quiet=0,alreadyquiet=0,mode=0;
160 	char *songname = NULL, *artist = NULL, *album = NULL, *year = NULL,
161 	*comment = NULL, *tracknum = NULL, *genre = NULL;
162 
163 	if (argc<2){
164 		print_help();
165 	}
166 	else {
167 		while (EOF != (c = getopt(argc, argv, "s:n:a:y:c:k:g:qSNAYCKGlLrimMv"))){
168 			switch (c){
169 				case 's':
170 					songname = optarg;
171 					break;
172 				case 'n':
173 					artist = optarg;
174 					break;
175 				case 'a':
176 					album = optarg;
177 					break;
178 				case 'y':
179 					year = optarg;
180 					break;
181 				case 'c':
182 					comment = optarg;
183 					break;
184 				case 'g':
185 					genre = optarg;
186 					break;
187 				case 'k':
188 					tracknum = optarg;
189 					break;
190 				case 'q':
191 #define REALQUIET() {if (!alreadyquiet) { quiet = 0xffff & ~QUIET_PROGRESS; alreadyquiet=1;}}
192 #define UNQUIET(b) { REALQUIET(); quiet &= ~b;}
193 					if(alreadyquiet)
194 						quiet |= QUIET_PROGRESS;
195 					else {
196 						REALQUIET();
197 					}
198 					break;
199 				case 'S':
200 					UNQUIET(QUIET_SONG);
201 					break;
202 				case 'N':
203 					UNQUIET(QUIET_ARTIST);
204 					break;
205 				case 'A':
206 					UNQUIET(QUIET_ALBUM);
207 					break;
208 				case 'Y':
209 					UNQUIET(QUIET_YEAR);
210 					break;
211 				case 'C':
212 					UNQUIET(QUIET_COMMENT);
213 					break;
214 				case 'G':
215 					UNQUIET(QUIET_GENRE);
216 					break;
217 				case 'K':
218 					UNQUIET(QUIET_TRACK);
219 					break;
220 				case 'l':
221 					print_genre_list(0);
222 					break;
223 				case 'L':
224 					print_genre_list(1);
225 					break;
226 				case 'r':
227 					mode=1;
228 					break;
229 				case 'i':
230 					mode=2;
231 					break;
232 				case 'v':
233 					version();
234 					license();
235 					break;
236 				case ':':
237 				case '?':
238 					//fprintf(stderr, "Illegal parameters\n");
239 					print_help();
240 					return 1;
241 				case 'm':
242 					mode=3;
243 					break;
244 				case 'M':
245 					mode=4;
246 					break;
247 			}
248 		}
249 		//ntf("quiet=%i alreadyquiet=%i\n",quiet,alreadyquiet);
250 		for (int i=optind;i<argc;i++)
251 			switch (mode){
252 				case 3:
253 				case 4:
254 					i3rename(argv[i],quiet,mode==3);
255 					break;
256 				case 2:
257 					i3info(argv[i],quiet);
258 					break;
259 				case 1:
260 					i3remove(argv[i],quiet);
261 					break;
262 				default:
263 					i3edit(argv[i],songname,artist,album,year,comment,tracknum,genre,quiet);
264 			}
265 	}
266 	return 0;
267 }
268 
print_genre_list(int mode)269 void print_genre_list(int mode){
270 	int c=0,w=0;
271 #ifdef HAVE_LIBCURSES
272 	c=COLS;
273 #endif
274 	if (c<=0)c=80;
275 	w=(c/26);
276 	if (w<1)w=1;
277 //	printf("c=%i w=%i\n",c,w);
278 	//for (int x=0, y=0, i=0;genre_list[i];mode?i++:(i=y+x*((NUM_GENRE+w-1)/w))){
279 	for (int x=0, y=0, i=0;genre_list[i];mode?i++:(i=y+x*((NUM_GENRE)/w))){
280 		printf("%3i: %-20s",i,genre_list[i]);
281 		//			       printf("x%i,y%i,i%i\n",x,y,i);
282 		if (x>=w-1){
283 			x=0;y++;
284 		}else{
285 			x++;
286 		}
287 		if (x==0){
288 			if (i==NUM_GENRE-1)
289 				break;
290 			printf("\n");
291 		}
292 		else
293 			printf(" ");
294 	}
295 	printf("\n");
296 }
297 
agenretoi(const char * g)298 int agenretoi(const char * g){
299 	int cmpl=g?strlen(g):0;
300 	while (cmpl>0 && isspace(g[cmpl-1]))
301 		--cmpl;//ignore trailing spaces
302 	if (cmpl>0){
303 		int i;
304 		char * errpos;
305 		for(i=0;i<NUM_GENRE;i++)
306 			if(strncasecmp(genre_list[i],g,cmpl)==0)
307 				return i;
308 		i=strtol(g,&errpos,0);
309 		if (!*errpos)
310 			return i;
311 	}
312 	printf("'%s' is not a valid genre name, nor a number between 0 and 255\n",g);
313 	return -1;
314 }
315 
316 #ifdef HAVE_LIBREADLINE
317 char *my_rl_default;
318 
set_rl_default(void)319 int set_rl_default(void){
320 	rl_insert_text(my_rl_default);
321 	//rl_forced_update_display();
322 	return 0;
323 }
324 #ifdef HAVE_SET_H
325 #include <set.h>
operator ()ltstr326 struct ltstr{  bool operator()(const char* s1, const char* s2) const {return strcasecmp(s1, s2) < 0;}  };
genre_generator(const char * text,int state)327 const char *genre_generator(const char *text,int state){
328 	static set<const char*, ltstr> matches;
329 	static set<const char*, ltstr>::iterator curm=matches.end();
330 	const char *ret;
331 	if (state==0){
332 		int i;
333 		int l=strlen(text);
334 		matches.erase(matches.begin(),matches.end());
335 		for (i=0;i<NUM_GENRE;i++)
336 			if (strncasecmp(genre_list[i],text,l)==0)
337 				matches.insert(genre_list[i]);
338 		curm=matches.begin();
339 	}
340 	if (curm==matches.end())return NULL;
341 	ret=(*curm);
342 	++curm;
343 	return strdup(ret);//return must be malloc()'d
344 }
345 #endif //HAVE_SET_H
346 #endif //HAVE_LIBREADLINE
genreedit(unsigned char & d)347 void genreedit(unsigned char &d){
348 	char def[40];
349 	int g=-1;
350 	if (d<NUM_GENRE)
351 		strcpy(def,genre_list[d]);
352 	else
353 		sprintf(def,"%i",d);
354 #ifdef HAVE_LIBREADLINE
355 	char *str=NULL;
356 	my_rl_default=def;
357 	rl_startup_hook=(Function*)set_rl_default;
358 #ifdef HAVE_SET_H
359 	(const char *(*)(const char *,int))rl_completion_entry_function=genre_generator;
360 #endif
361 //	rl_attempted_completion_function = (CPPFunction *)fileman_completion;
362 #else
363 	char str[40];
364 #endif
365 
366 	do {
367 #ifdef HAVE_LIBREADLINE
368 		if (str){strcpy(def,str);free(str);}
369 		if((str=readline("genre[0-255/name]: "))){
370 			if (*str){
371 				add_history(str);
372 			}
373 		}
374 #else
375 		printf("genre[0-255/name def:%s]: ",def);
376 		fflush(stdout);
377 		if (fgets(str,40,stdin)){
378 			char *t;
379 			if ((t=strchr(str,'\n')))
380 				t[0]=0;
381 		}
382 		if(!*str) {
383 			// Blank; accept default.
384 			g = d;
385 			break;
386 		}
387 
388 #endif
389 		if(str) g = agenretoi(str);
390 	} while (g == -1);
391 
392 #ifdef HAVE_LIBREADLINE
393 	free(str);
394 	rl_completion_entry_function=NULL;//use normal completer again.
395 #endif
396 	d=g;
397 }
stredit(const char * name,int maxlen,char * buf)398 void stredit(const char * name, int maxlen, char * buf){
399 	char def[40];
400 	strncpy(def,buf,maxlen);
401 	def[maxlen]=0;
402 #ifdef HAVE_LIBREADLINE
403 	char prompt[40];
404 	char *str;
405 	sprintf(prompt,"%s[max:%i]: ",name,maxlen);
406 	my_rl_default=def;
407 	rl_startup_hook=(Function*)set_rl_default;
408 	if((str=readline(prompt))){
409 		strncpy(buf,str,maxlen);
410 		if (*str){
411 			add_history(str);
412 		}
413 		free(str);
414 	}
415 #else
416 	char str[40];
417 	printf("%s[max:%i def:%s]: ",name,maxlen,def);
418 	fflush(stdout);
419 	if (fgets(str,40,stdin)){
420 		char *t;
421 		if ((t=strchr(str,'\n')))
422 			t[0]=0;
423 		if (str[0])
424 			strncpy(buf,str,maxlen);
425 	}else if (ferror(stdin))
426 		perror("fgets");
427 	else
428 		strncpy(buf,"",maxlen);
429 #endif
430 }
431 
432 //##### TODO: make renaming configurable?
i3rename(const char * file,int quiet,int test)433 void i3rename(const char *file,int quiet,int test){
434 	int f;
435 	if (doopen(f,file,RDMODE))return;
436 	if ((!quiet) || test){
437 		printf("%s->",file);fflush(stdout);
438 	}
439 	off_t end=lseek(f,0,SEEK_END);
440 	if (end>=0 && end<128) {
441 		close (f);
442 		if ((!quiet))
443 			printf("(no tag)\n");
444 		return;
445 	}
446 	if (lseek(f,-128,SEEK_END)<0){
447 		close (f);
448 		perror("lseek");return;
449 	}
450 	if (doread(f,&id3,sizeof(id3),"id3buf"))return;
451 	close (f);
452 	if (!strncmp(id3.tag,"TAG",3)){
453 		char newname[256];
454 		int track=0;
455 		if (id3.v11.empty==0)
456 			track=id3.v11.tracknum;
457 		sprintf(newname,"%02i-%.*s.mp3",track,(int)sizeof(id3.songname),id3.songname);//##### TODO: handle directories in file, handle invalid chars in songname
458 		if ((!quiet) || test){
459 			printf("%s\n",newname);
460 		}
461 		if (!test){
462 			if (rename(file,newname))
463 				perror("rename");return;
464 		}
465 	}else
466 		if ((!quiet))
467 			printf("(no tag)\n");
468 }
469 
i3info(const char * file,int quiet)470 void i3info(const char *file,int quiet){
471 	int f;
472 	if (doopen(f,file,RDMODE))return;
473 	printf("%s: ",file);fflush(stdout);
474 	off_t end=lseek(f,0,SEEK_END);
475 	if (end>=0 && end<128) {
476 		close (f);
477 		if ((!quiet))
478 			printf("(no tag)\n");
479 		return;
480 	}
481 	if (lseek(f,-128,SEEK_END)<0){
482 		close (f);
483 		perror("lseek");return;
484 	}
485 	if (doread(f,&id3,sizeof(id3),"id3buf"))return;
486 	if (!strncmp(id3.tag,"TAG",3)){
487 		if (id3.v11.empty==0)
488 			printf("(tag v1.1%s)\n",id3.v11.tracknum?"":"?");
489 		else
490 			printf("(tag v1.0)\n");
491 #define PRINTINFO(f) printf("%s: %.*s\n",#f, (int)sizeof(id3.f), id3.f)
492 		PRINTINFO(songname);
493 		PRINTINFO(artist);
494 		PRINTINFO(album);
495 		PRINTINFO(year);
496 		PRINTINFO(comment);
497 		if (id3.v11.empty==0)
498 			printf("tracknum: %i\n", id3.v11.tracknum);
499 		printf("genre: %s(%i)\n\n",(id3.genre<NUM_GENRE)?genre_list[id3.genre]:"unknown",id3.genre);
500 
501 	}else
502 		if ((!quiet))
503 			printf("(no tag)\n");
504 
505 	close (f);
506 }
507 
i3remove(const char * file,int quiet)508 void i3remove(const char *file,int quiet){
509 	int f;
510 	if (doopen(f,file,RDWRMODE))return;
511 	off_t end=lseek(f,0,SEEK_END);
512 	if (end>=0 && end<128) {
513 		close (f);
514 		if ((!quiet))
515 			printf("no tag in %s\n",file);
516 		return;
517 	}
518 	if ((end=lseek(f,-128,SEEK_END))<0){
519 		close (f);
520 		perror("lseek");return;
521 	}
522 	if (doread(f,&id3,sizeof(id3),"id3buf"))return;
523 	if (!strncmp(id3.tag,"TAG",3)){
524 		ftruncate(f,end);
525 		if ((!quiet))
526 			printf("tag removed from %s\n",file);
527 	}else
528 		if ((!quiet))
529 			printf("no tag in %s\n",file);
530 
531 	close (f);
532 }
533 
i3edit(const char * file,const char * defsongname,const char * defartist,const char * defalbum,const char * defyear,const char * defcomment,const char * deftracknum,const char * defgenre,int quiet)534 void i3edit(const char * file, const char * defsongname,
535 		const char * defartist, const char * defalbum,
536 		const char * defyear, const char * defcomment,
537 		const char * deftracknum, const char * defgenre, int quiet){
538 	int f,hasid3,commentsize=28;
539 	if (!(quiet&QUIET_PROGRESS))
540 		printf("\nFile %s: ", file);
541 	if (doopen(f,file,RDWRMODE))return;
542 	off_t end=lseek(f,0,SEEK_END);
543 	if (end>=0 && end<128) {
544 		memset(&id3,0,sizeof(id3));
545 		id3.tag[0]='T';id3.tag[1]='A';id3.tag[2]='G';
546 		id3.genre=255;
547 		hasid3=0;
548 		if (!(quiet&QUIET_PROGRESS))
549 			printf("(no tag)\n");
550 	} else {
551 		if (lseek(f,-128,SEEK_END)<0){
552 			close(f);
553 			perror("lseek");return;
554 		}
555 		if (doread(f,&id3,sizeof(id3),"id3buf"))return;
556 		if (strncmp(id3.tag,"TAG",3)){
557 			memset(&id3,0,sizeof(id3));
558 			id3.tag[0]='T';id3.tag[1]='A';id3.tag[2]='G';
559 			id3.genre=255;
560 			hasid3=0;
561 			if (!(quiet&QUIET_PROGRESS))
562 				printf("(no tag)\n");
563 		} else {
564 			if (id3.v11.empty==0){
565 				if (!(quiet&QUIET_PROGRESS))
566 					printf("(tag v1.1)\n");
567 				hasid3=2;//id3 v1.1
568 			}else{
569 				if (!(quiet&1))
570 					printf("(tag v1.0");
571 				if (deftracknum){
572 					hasid3=2;
573 					if (!(quiet&QUIET_PROGRESS))
574 						printf("->1.1)\n");
575 				}else{
576 					hasid3=1;//id3 v1.0
577 					commentsize=30;
578 					if (!(quiet&QUIET_PROGRESS))
579 						printf(")\n");
580 				}
581 			}
582 		}
583 	}
584 	if (defsongname){
585 		memset(id3.songname,0,sizeof(id3.songname));
586 		strncpy(id3.songname,defsongname,sizeof(id3.songname));
587 	}
588 	if (defartist){
589 		memset(id3.artist,0,sizeof(id3.artist));
590 		strncpy(id3.artist,defartist,sizeof(id3.artist));
591 	}
592 	if (defalbum){
593 		memset(id3.album,0,sizeof(id3.album));
594 		strncpy(id3.album,defalbum,sizeof(id3.album));
595 	}
596 	if (defyear){
597 		memset(id3.year,0,sizeof(id3.year));
598 		strncpy(id3.year,defyear,sizeof(id3.year));
599 	}
600 	if (defcomment){
601 		memset(id3.comment,0,commentsize);
602 		strncpy(id3.comment,defcomment,commentsize);
603 	}
604 	if (deftracknum){
605 		//memset(id3.tracknum,0,sizeof(id3.tracknum));
606 		id3.v11.empty=0;
607 		id3.v11.tracknum = atoi(deftracknum);
608 		//strncpy(id3.tracknum,deftracknum,sizeof(id3.tracknum));
609 	}
610 	if (defgenre){
611 		id3.genre = agenretoi(defgenre);
612 		//unsigned tmp;
613 		//sscanf(defgenre,"%u",&tmp);
614 		//id3.genre = tmp;
615 	}
616 	if (!(quiet&QUIET_SONG))
617 		stredit("songname",30,id3.songname);
618 	if (!(quiet&QUIET_ARTIST))
619 		stredit("artist",30,id3.artist);
620 	if (!(quiet&QUIET_ALBUM))
621 		stredit("album",30,id3.album);
622 	if (!(quiet&QUIET_YEAR))
623 		stredit("year",4,id3.year);
624 	if (!(quiet&QUIET_COMMENT))
625 		stredit("comment",commentsize,id3.comment);
626 	if (hasid3!=1 && !(quiet&QUIET_TRACK)) {
627 		char tbuf[3];
628 		sprintf(tbuf, "%i", id3.v11.tracknum);
629 		stredit("tracknum", 3, tbuf);
630 		id3.v11.empty=0;
631 		id3.v11.tracknum=atoi(tbuf);
632 	}
633 	if (!(quiet&QUIET_GENRE)){
634 		//char sbuf[4];
635 		//sprintf(sbuf,"%i",id3.genre);
636 		//stredit("genre",3,sbuf);
637 		//id3.genre=atoi(sbuf);
638 		genreedit(id3.genre);
639 	}
640 	if (hasid3){
641 		if (lseek(f,-128,SEEK_END)<0){
642 			perror("lseek");return;
643 		}
644 	}else{
645 		if (lseek(f,0,SEEK_END)<0){
646 			perror("lseek");return;
647 		}
648 	}
649 	if (dowrite(f,&id3,sizeof(id3),"id3buf"))return;
650 	close(f);
651 }
652