/** * Returns the offset of the atom specified by the given path or -1 if * not found. atom_path is a colon separated list of atoms specifying * a path from parent node to the target node. All paths must be specified * from the root. * * @param aac_fp the aac file being searched * @param atom_path the colon separated list of atoms * @param atom_length the size of the atom "drilled to" * @returns offset of the atom, or -1 if unsuccessful */ uint64_t scan_aac_drilltoatom(IOHANDLE hfile,char *atom_path, unsigned int *atom_length) { uint64_t atom_offset; uint64_t file_size,pos; char *cur_p, *end_p; char atom_name[5]; DPRINTF(E_SPAM,L_SCAN,"Searching for %s\n",atom_path); // FIXME: cache io_size for static files io_size(hfile, &file_size); io_setpos(hfile,0,SEEK_SET); end_p = atom_path; while (*end_p != '\0') { end_p++; } atom_name[4] = '\0'; cur_p = atom_path; while (cur_p != NULL) { if ((end_p - cur_p) < 4) { return -1; } strncpy(atom_name, cur_p, 4); atom_offset = scan_aac_findatom(hfile, file_size, atom_name, atom_length); if (atom_offset == -1) { return -1; } io_getpos(hfile,&pos); DPRINTF(E_SPAM,L_SCAN,"Found %s atom at off %lld.\n", atom_name, pos - 8); cur_p = strchr(cur_p, ':'); if (cur_p != NULL) { cur_p++; /* FIXME * Hack to deal with atoms that have extra data in addition * to having child atoms. This should be dealt in a better fashion * than this (table with skip offsets or a real mp4 parser.) */ if (!strcmp(atom_name, "meta")) { io_setpos(hfile, 4, SEEK_CUR); } else if (!strcmp(atom_name, "stsd")) { io_setpos(hfile, 8, SEEK_CUR); } else if (!strcmp(atom_name, "mp4a")) { io_setpos(hfile, 28, SEEK_CUR); } } } io_getpos(hfile, &pos); return pos - 8; }
/** * Given a file, search for a particular aac atom. * * @param fin file handle of the open aac file * @param max_offset how far to search (probably size of container atom) * @param which_atom what atom name we are searching for * @param atom_size this will hold the size of the atom found */ int scan_aac_findatom(IOHANDLE hfile, uint64_t max_offset, uint64_t *atom_offset, char *which_atom, unsigned int *atom_size) { uint64_t current_offset=0; uint32_t size; char atom[4]; uint32_t bytes_read; while((current_offset + 8) < max_offset) { bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char *)&size,&bytes_read) || (!bytes_read)) { return FALSE; } size=ntohl(size); if(size <= 7) { /* something not right */ DPRINTF(E_LOG,L_SCAN,"Bad aac file: atom length too short searching for %s\n", which_atom); return FALSE; } bytes_read = 4; if(!io_read(hfile,(unsigned char *)atom,&bytes_read) || (!bytes_read)) { return FALSE; } if(strncasecmp(atom,which_atom,4) == 0) { *atom_size=size; *atom_offset = current_offset; return TRUE; } io_setpos(hfile,size-8,SEEK_CUR); current_offset+=size; } DPRINTF(E_SPAM,L_SCAN,"Couldn't find atom %s as requested\n",which_atom); return FALSE; }
/** * main aac scanning routing. * * @param filename file to scan * @param pmp3 pointer to the MP3FILE to fill with data * @returns FALSE if file should not be added to database, TRUE otherwise */ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) { IOHANDLE hfile; uint64_t atom_offset, pos; uint32_t bytes_read; unsigned int atom_length; long current_offset=0; uint32_t current_size; char current_atom[4]; char *current_data; unsigned short us_data; int genre; int len; uint32_t sample_size; uint32_t samples; uint32_t bit_rate; int ms; unsigned char buffer[2]; uint32_t time = 0; hfile = io_new(); if(!hfile) DPRINTF(E_FATAL,L_SCAN,"Malloc error in scan_get_aacinfo\n"); if(!io_open(hfile,"file://%U",filename)) { DPRINTF(E_INF,L_SCAN,"Cannot open file %s for reading: %s\n",filename, io_errstr(hfile)); io_dispose(hfile); return FALSE; } atom_offset=scan_aac_drilltoatom(hfile, "moov:udta:meta:ilst", &atom_length); if(atom_offset != -1) { /* found the tag section - need to walk through now */ while(current_offset < (uint64_t)atom_length) { bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char *)¤t_size,&bytes_read) || !bytes_read) { DPRINTF(E_LOG,L_SCAN,"Error reading mp4 atoms: %s\n",io_errstr(hfile)); io_dispose(hfile); return FALSE; } current_size=ntohl(current_size); DPRINTF(E_SPAM,L_SCAN,"Current size: %d\n",current_size); if(current_size <= 7) { /* something not right */ DPRINTF(E_LOG,L_SCAN,"mp4 atom too small. Bad aac tags?\n"); io_dispose(hfile); return FALSE; } bytes_read = 4; if(!io_read(hfile,(unsigned char *)current_atom,&bytes_read) || !bytes_read) { DPRINTF(E_LOG,L_SCAN,"Error reading mp4 atoms: %s\n",io_errstr(hfile)); io_dispose(hfile); return FALSE; } DPRINTF(E_SPAM,L_SCAN,"Current Atom: %c%c%c%c\n", current_atom[0],current_atom[1],current_atom[2], current_atom[3]); if(current_size > 4096) { /* Does this break anything? */ /* too big! cover art, maybe? */ io_setpos(hfile,current_size - 8, SEEK_CUR); DPRINTF(E_SPAM,L_SCAN,"Atom too big... skipping\n"); } else { len=current_size-7; /* for ill-formed too-short tags */ if(len < 22) { len=22; } current_data=(char*)malloc(len); /* extra byte */ memset(current_data,0x00,len); bytes_read = current_size - 8; if(!io_read(hfile,(unsigned char *)current_data,&bytes_read) || (!bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading mp4 data: %s\n",io_errstr(hfile)); free(current_data); io_dispose(hfile); return FALSE; } if(!memcmp(current_atom,"\xA9" "nam",4)) { /* Song name */ pmp3->title=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"aART",4)) { pmp3->album_artist=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "ART",4)) { pmp3->artist=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "alb",4)) { pmp3->album=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "cmt",4)) { pmp3->comment=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "wrt",4)) { pmp3->composer=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "grp",4)) { pmp3->grouping=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"\xA9" "gen",4)) { /* can this be a winamp genre??? */ pmp3->genre=strdup((char*)¤t_data[16]); } else if(!memcmp(current_atom,"tmpo",4)) { us_data=*((unsigned short *)¤t_data[16]); us_data=ntohs(us_data); pmp3->bpm=us_data; } else if(!memcmp(current_atom,"trkn",4)) { us_data=*((unsigned short *)¤t_data[18]); us_data=ntohs(us_data); pmp3->track=us_data; us_data=*((unsigned short *)¤t_data[20]); us_data=ntohs(us_data); pmp3->total_tracks=us_data; } else if(!memcmp(current_atom,"disk",4)) { us_data=*((unsigned short *)¤t_data[18]); us_data=ntohs(us_data); pmp3->disc=us_data; us_data=*((unsigned short *)¤t_data[20]); us_data=ntohs(us_data); pmp3->total_discs=us_data; } else if(!memcmp(current_atom,"\xA9" "day",4)) { pmp3->year=atoi((char*)¤t_data[16]); } else if(!memcmp(current_atom,"gnre",4)) { genre=(int)(*((char*)¤t_data[17])); genre--; if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN)) genre=WINAMP_GENRE_UNKNOWN; pmp3->genre=strdup(scan_winamp_genre[genre]); } else if (!memcmp(current_atom, "cpil", 4)) { pmp3->compilation = current_data[16]; } free(current_data); } current_offset+=current_size; } } /* got the tag info, now let's get bitrate, etc */ atom_offset = scan_aac_drilltoatom(hfile, "moov:mvhd", &atom_length); if(atom_offset != -1) { io_setpos(hfile,4,SEEK_CUR); /* FIXME: error handling */ bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char *)&time, &bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading time from moov:mvhd: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } time = ntohl(time); pmp3->time_added = (int)scan_aac_mac_to_unix_time(time); bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char *)&time, &bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading time from moov:mvhd: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } time = ntohl(time); pmp3->time_modified = (int)scan_aac_mac_to_unix_time(time); bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char *)&sample_size,&bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading sample_size from moov:mvhd: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } bytes_read = sizeof(uint32_t); if(!io_read(hfile,(unsigned char*)&samples, &bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading samples from moov:mvhd: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } sample_size=ntohl(sample_size); samples=ntohl(samples); /* avoid overflowing on large sample_sizes (90000) */ ms=1000; while((ms > 9) && (!(sample_size % 10))) { sample_size /= 10; ms /= 10; } /* DWB: use ms time instead of sec */ pmp3->song_length=(uint32_t)((samples * ms) / sample_size); DPRINTF(E_DBG,L_SCAN,"Song length: %d seconds\n", pmp3->song_length / 1000); } pmp3->bitrate = 0; /* see if it is aac or alac */ atom_offset = scan_aac_drilltoatom(hfile, "moov:trak:mdia:minf:stbl:stsd:alac", &atom_length); if(atom_offset != -1) { /* should we still pull samplerate, etc from the this atom? */ if(pmp3->codectype) { free(pmp3->codectype); } pmp3->codectype=strdup("alac"); } /* Get the sample rate from the 'mp4a' atom (timescale). This is also found in the 'mdhd' atom which is a bit closer but we need to navigate to the 'mp4a' atom anyways to get to the 'esds' atom. */ atom_offset=scan_aac_drilltoatom(hfile, "moov:trak:mdia:minf:stbl:stsd:mp4a", &atom_length); if(atom_offset == -1) { atom_offset=scan_aac_drilltoatom(hfile, "moov:trak:mdia:minf:stbl:stsd:drms", &atom_length); } if (atom_offset != -1) { io_setpos(hfile, atom_offset + 32, SEEK_SET); /* Timescale here seems to be 2 bytes here (the 2 bytes before it are * "reserved") though the timescale in the 'mdhd' atom is 4. Not sure * how this is dealt with when sample rate goes higher than 64K. */ bytes_read = 2; if(!io_read(hfile, (unsigned char *)buffer, &bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading timescale from drms atom: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } pmp3->samplerate = (buffer[0] << 8) | (buffer[1]); /* Seek to end of atom. */ io_setpos(hfile, 2, SEEK_CUR); /* Get the bit rate from the 'esds' atom. We are already positioned in the parent atom so just scan ahead. */ io_getpos(hfile,&pos); atom_offset = scan_aac_findatom(hfile, atom_length-(pos-atom_offset), "esds", &atom_length); if (atom_offset != -1) { /* Roku Soundbridge seems to believe anything above 320K is * an ALAC encoded m4a. We'll lie on their behalf. */ io_setpos(hfile, atom_offset + 22, SEEK_CUR); bytes_read = sizeof(unsigned int); if(!io_read(hfile, (unsigned char *)&bit_rate, &bytes_read)) { DPRINTF(E_LOG,L_SCAN,"Error reading bitrate from esds: %s\n", io_errstr(hfile)); io_dispose(hfile); return FALSE; } pmp3->bitrate = ntohl(bit_rate) / 1000; DPRINTF(E_DBG,L_SCAN,"esds bitrate: %d\n",pmp3->bitrate); if(pmp3->bitrate > 320) { pmp3->bitrate = 320; } } else { DPRINTF(E_DBG,L_SCAN, "Couldn't find 'esds' atom for bit rate.\n"); } } else { DPRINTF(E_DBG,L_SCAN, "Couldn't find 'mp4a' atom for sample rate.\n"); } /* Fallback if we can't find the info in the atoms. */ if (pmp3->bitrate == 0) { /* calculate bitrate from song length... Kinda cheesy */ DPRINTF(E_DBG,L_SCAN, "Guesstimating bit rate.\n"); atom_offset=scan_aac_drilltoatom(hfile,"mdat",&atom_length); if ((atom_offset != -1) && (pmp3->song_length >= 1000)) { pmp3->bitrate = atom_length / ((pmp3->song_length / 1000) * 128); } } io_close(hfile); io_dispose(hfile); return TRUE; /* we'll return as much as we got. */ }