| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #include "mac.h" | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | static int | 
| 4 | 2 |  |  |  |  |  | get_macfileinfo(PerlIO *infile, char *file, HV *info) | 
| 5 |  |  |  |  |  |  | { | 
| 6 |  |  |  |  |  |  | Buffer header; | 
| 7 |  |  |  |  |  |  | char *bptr; | 
| 8 | 2 |  |  |  |  |  | int32_t ret = 0; | 
| 9 |  |  |  |  |  |  | int32_t header_end; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | mac_streaminfo *si; | 
| 12 | 2 |  |  |  |  |  | Newz(0, si, sizeof(mac_streaminfo), mac_streaminfo); | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | /* | 
| 15 |  |  |  |  |  |  | There are two possible variations here. | 
| 16 |  |  |  |  |  |  | 1.  There's an ID3V2 tag present at the beginning of the file | 
| 17 |  |  |  |  |  |  | 2.  There's an APE tag present at the beginning of the file | 
| 18 |  |  |  |  |  |  | (deprecated, but still possible) | 
| 19 |  |  |  |  |  |  | For each type of tag, check for existence and then skip it before | 
| 20 |  |  |  |  |  |  | looking for the MPC header | 
| 21 |  |  |  |  |  |  | */ | 
| 22 | 2 | 50 |  |  |  |  | if ((header_end = skip_id3v2(infile)) < 0) { | 
| 23 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't skip ID3v2]: %s\n", file); | 
| 24 | 0 |  |  |  |  |  | Safefree(si); | 
| 25 | 0 |  |  |  |  |  | return -1; | 
| 26 |  |  |  |  |  |  | } | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | // seek to first byte of MAC data | 
| 29 | 2 | 50 |  |  |  |  | if (PerlIO_seek(infile, header_end, SEEK_SET) < 0) { | 
| 30 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't seek to offset %d]: %s\n", header_end, file); | 
| 31 | 0 |  |  |  |  |  | Safefree(si); | 
| 32 | 0 |  |  |  |  |  | return -1; | 
| 33 |  |  |  |  |  |  | } | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | // Offset + MAC. Does this need the space as well, to be +4 ? | 
| 36 | 2 |  |  |  |  |  | si->audio_start_offset = PerlIO_tell(infile) + 3; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | // Skip the APETAGEX if it exists. | 
| 39 | 2 |  |  |  |  |  | buffer_init(&header, APE_HEADER_LEN); | 
| 40 |  |  |  |  |  |  |  | 
| 41 | 2 | 50 |  |  |  |  | if (!_check_buf(infile, &header, APE_HEADER_LEN, APE_HEADER_LEN)) { | 
| 42 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't read tag header]: %s\n", file); | 
| 43 | 0 |  |  |  |  |  | goto out; | 
| 44 |  |  |  |  |  |  | } | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 2 |  |  |  |  |  | bptr = buffer_ptr(&header); | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 2 | 50 |  |  |  |  | if (memcmp(bptr, "APETAGEX", 8) == 0) { | 
| 49 |  |  |  |  |  |  | // Skip the ape tag structure | 
| 50 |  |  |  |  |  |  | // XXXX - need to test this code path. | 
| 51 | 0 |  |  |  |  |  | buffer_get_int_le(&header); | 
| 52 | 0 |  |  |  |  |  | PerlIO_seek(infile, buffer_get_int_le(&header), SEEK_CUR); | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | } else { | 
| 55 |  |  |  |  |  |  | // set the pointer back to original location | 
| 56 | 2 |  |  |  |  |  | PerlIO_seek(infile, -APE_HEADER_LEN, SEEK_CUR); | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 2 |  |  |  |  |  | buffer_clear(&header); | 
| 60 |  |  |  |  |  |  |  | 
| 61 | 2 | 50 |  |  |  |  | if (!_check_buf(infile, &header, 32, 32)) { | 
| 62 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't read stream header]: %s\n", file); | 
| 63 | 0 |  |  |  |  |  | goto out; | 
| 64 |  |  |  |  |  |  | } | 
| 65 |  |  |  |  |  |  |  | 
| 66 | 2 |  |  |  |  |  | bptr = buffer_ptr(&header); | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 2 | 50 |  |  |  |  | if (memcmp(bptr, "MAC ", 4) != 0) { | 
| 69 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't couldn't find stream header]: %s\n", file); | 
| 70 | 0 |  |  |  |  |  | goto out; | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 2 |  |  |  |  |  | buffer_consume(&header, 4); | 
| 74 | 2 |  |  |  |  |  | si->version = buffer_get_short_le(&header); | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 2 | 50 |  |  |  |  | if (si->version < 3980) { | 
| 77 | 0 |  |  |  |  |  | uint16_t compression_id = buffer_get_short_le(&header); | 
| 78 | 0 | 0 |  |  |  |  | if (compression_id % 1000) { | 
| 79 | 0 |  |  |  |  |  | si->compression = ""; | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  | else { | 
| 82 | 0 |  |  |  |  |  | si->compression = mac_profile_names[ compression_id / 1000 ]; | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 0 | 0 |  |  |  |  | if (!_check_buf(infile, &header, MAC_397_HEADER_LEN, MAC_397_HEADER_LEN)) { | 
| 86 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't read < 3.98 stream header]: %s\n", file); | 
| 87 | 0 |  |  |  |  |  | goto out; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 | 0 |  |  |  |  |  | buffer_consume(&header, 2); // flags | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 0 |  |  |  |  |  | si->channels = buffer_get_short_le(&header); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 0 |  |  |  |  |  | si->sample_rate = buffer_get_int_le(&header); | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 0 |  |  |  |  |  | buffer_consume(&header, 4); // header size | 
| 97 | 0 |  |  |  |  |  | buffer_consume(&header, 4); // terminating data bytes | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 0 |  |  |  |  |  | si->total_frames      = buffer_get_int_le(&header); | 
| 100 | 0 |  |  |  |  |  | si->final_frame       = buffer_get_int_le(&header); | 
| 101 | 0 | 0 |  |  |  |  | si->blocks_per_frame  = si->version >= 3950 ? (73728 * 4) : 73728; | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | } else { | 
| 104 |  |  |  |  |  |  | unsigned char md5[16]; | 
| 105 |  |  |  |  |  |  | uint16_t profile; | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 2 | 50 |  |  |  |  | if (!_check_buf(infile, &header, MAC_398_HEADER_LEN, MAC_398_HEADER_LEN)) { | 
| 108 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "MAC: [Couldn't read > 3.98 stream header]: %s\n", file); | 
| 109 | 0 |  |  |  |  |  | goto out; | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 2 |  |  |  |  |  | buffer_consume(&header, 2); | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | // unused. | 
| 115 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // desc bytes | 
| 116 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // header bytes | 
| 117 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // seek table bytes | 
| 118 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // header data bytes | 
| 119 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // ape frame data bytes | 
| 120 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // ape frame data bytes high | 
| 121 | 2 |  |  |  |  |  | buffer_get_int_le(&header); // terminating data bytes | 
| 122 | 2 |  |  |  |  |  | buffer_get(&header, &md5, sizeof(md5)); | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | // Header block | 
| 125 | 2 |  |  |  |  |  | profile = buffer_get_short_le(&header); | 
| 126 | 2 | 50 |  |  |  |  | if (profile % 1000) { | 
| 127 | 0 |  |  |  |  |  | si->compression = ""; | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  | else { | 
| 130 | 2 |  |  |  |  |  | si->compression = mac_profile_names[ profile / 1000 ]; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 2 |  |  |  |  |  | buffer_get_short_le(&header); // flags | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 2 |  |  |  |  |  | si->blocks_per_frame  = buffer_get_int_le(&header); | 
| 136 | 2 |  |  |  |  |  | si->final_frame       = buffer_get_int_le(&header); | 
| 137 | 2 |  |  |  |  |  | si->total_frames      = buffer_get_int_le(&header); | 
| 138 | 2 |  |  |  |  |  | si->bits              = buffer_get_short_le(&header); | 
| 139 | 2 |  |  |  |  |  | si->channels          = buffer_get_short_le(&header); | 
| 140 | 2 |  |  |  |  |  | si->sample_rate       = buffer_get_int_le(&header); | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 2 |  |  |  |  |  | si->file_size = _file_size(infile); | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 2 | 50 |  |  |  |  | if (si->sample_rate) { | 
| 146 | 2 |  |  |  |  |  | double total_samples = (double)(((si->blocks_per_frame * (si->total_frames - 1)) + si->final_frame)); | 
| 147 | 2 |  |  |  |  |  | uint32_t total_ms = (total_samples * 1000) / si->sample_rate; | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 2 |  |  |  |  |  | my_hv_store(info, "samplerate", newSViv(si->sample_rate)); | 
| 150 | 2 |  |  |  |  |  | my_hv_store(info, "channels", newSViv(si->channels)); | 
| 151 | 2 |  |  |  |  |  | my_hv_store(info, "song_length_ms", newSVuv(total_ms)); | 
| 152 | 2 |  |  |  |  |  | my_hv_store(info, "bitrate", newSVuv( _bitrate(si->file_size - si->audio_start_offset, total_ms) )); | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 2 |  |  |  |  |  | my_hv_store(info, "file_size", newSVnv(si->file_size)); | 
| 155 | 2 |  |  |  |  |  | my_hv_store(info, "audio_offset", newSVuv(si->audio_start_offset)); | 
| 156 | 2 |  |  |  |  |  | my_hv_store(info, "audio_size", newSVuv(si->file_size - si->audio_start_offset)); | 
| 157 | 2 |  |  |  |  |  | my_hv_store(info, "compression", newSVpv(si->compression, 0)); | 
| 158 | 2 |  |  |  |  |  | my_hv_store(info, "version", newSVpvf( "%0.2f", si->version * 1.0 / 1000 ) ); | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | out: | 
| 162 | 2 |  |  |  |  |  | buffer_free(&header); | 
| 163 | 2 |  |  |  |  |  | Safefree(si); | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 2 |  |  |  |  |  | return ret; | 
| 166 |  |  |  |  |  |  | } |