/* id3.c by Michael Thorpe 2021-01-21 */ #include #include #include #include #include struct s_id3 { char tag[3]; char songname[30]; char artist[30]; char album[30]; char year[4]; char comment[30]; unsigned char genre; } id3; #define NUM_GENRES 148 static const char *genre_list[NUM_GENRES+1]={ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebop", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humor", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A Capella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "SynthPop", 0 }; static void print_linear_genre_list() { int i; for(i=0;genre_list[i];i++) printf("%d\t%s\n",i,genre_list[i]); } static void print_genre_list() { int i,j,cols; char *s; cols=80; s=getenv("COLUMNS"); if(s) { cols=atoi(s); if(cols<25) cols=25; } cols=cols/25; for(i=j=0;genre_list[i];i++) { printf("%3i: %-19s",i,genre_list[i]); if(++j0) putchar('\n'); } static int genre2int(const char *g) { int i; char *errpos; for(i=0;genre_list[i];i++) if(strcasecmp(genre_list[i],g)==0) return(i); i=strtol(g,&errpos,0); if(!*errpos) return(i); return(-1); } static void usage(int i) { fputs( "usage: id3 [-Llrw] [-s songname] [-n artist] [-a album] [-y year]\n" " [-c comment] [-g genre] \n" " -L display list of genres\n" " -l display list of genres in columns\n" " -r remove id3 tag from files\n" " -w remove spaces from end of tag values\n" ,i?stderr:stdout); exit(i); } int main(int argc,char **argv) { char *songname=0,*artist=0,*album=0,*year=0,*comment=0; int c,genre=-1,i,j,remove=0,whitespace=0; FILE *f; long l; while(EOF != (c=getopt(argc,argv,"La:c:g:hln:rs:wy:"))) { switch(c) { case 'L': print_linear_genre_list(1); return(0); case 'a': album=optarg; break; case 'c': comment=optarg; break; case 'g': genre=genre2int(optarg); if(genre==-1) { fprintf(stderr,"'%s' is not a valid genre name or number\n",optarg); return(1); } break; case 'h': usage(0); break; case 'l': print_genre_list(1); return(0); case 'n': artist=optarg; break; case 'r': remove=1; break; case 's': songname=optarg; break; case 'w': whitespace=1; break; case 'y': year=optarg; break; default: usage(1); break; } } if(optind==argc) usage(1); for(i=optind;i(l=ftell(f))) { perror("ftell"); fclose(f); continue; } if(1 != fread(&id3,sizeof(id3),1,f)) { perror("fread"); fclose(f); continue; } if(remove) { fclose(f); if(0==strncmp(id3.tag,"TAG",3)) { if(truncate(argv[i],l)) { perror("truncate"); fclose(f); } } } else { if(songname || artist || album || year || comment || genre!=-1) { if(0==strncmp(id3.tag,"TAG",3)) { cleanup_whitespace: if(0 != fseek(f,l,SEEK_SET)) { perror("fseek"); fclose(f); continue; } if(whitespace) { for(j=30-1;0<=j && ' '==id3.songname[j];j--) id3.songname[j]='\0'; for(j=30-1;0<=j && ' '==id3.artist[j];j--) id3.artist[j]='\0'; for(j=30-1;0<=j && ' '==id3.album[j];j--) id3.album[j]='\0'; for(j=4-1;0<=j && ' '==id3.year[j];j--) id3.year[j]='\0'; for(j=30-1;0<=j && ' '==id3.comment[j];j--) id3.comment[j]='\0'; } } else { memset(&id3,0,sizeof(id3)); strncpy(id3.tag,"TAG",3); id3.genre=12; /* Other */ } if(songname) strncpy(id3.songname,songname,30); if(artist) strncpy(id3.artist,artist,30); if(album) strncpy(id3.album,album,30); if(year) strncpy(id3.year,year,4); if(comment) strncpy(id3.comment,comment,30); if(genre!=-1) id3.genre=genre; if(1 != fwrite(&id3,sizeof(id3),1,f)) { perror("fread"); fclose(f); } } else { /* We fall through to this branch if whitespace is set without any other * setters being set, because if we don't have a tag at all we want to complain * about that rather than silently creating an empty tag. */ if(strncmp(id3.tag,"TAG",3)) { fputs("(no tag)\n",stdout); } else { if(whitespace) goto cleanup_whitespace; printf("Name: %.30s\n",id3.songname); printf("Artist: %.30s\n",id3.artist); printf("Album: %.30s\n",id3.album); printf("Year: %.4s\n",id3.year); printf("Comment: %.30s\n",id3.comment); printf("Genre: %d (%s)\n",id3.genre,(id3.genre>=NUM_GENRES?"unknown":genre_list[id3.genre])); } } fclose(f); } } return(0); }