//==================================================================// /* AtomicParsley - metalist.cpp AtomicParsley is GPL software; you can freely distribute, redistribute, modify & use under the terms of the GNU General Public License; either version 2 or its successor. AtomicParsley is distributed under the GPL "AS IS", without any warranty; without the implied warranty of merchantability or fitness for either an expressed or implied particular purpose. Please see the included GNU General Public License (GPL) for your rights and further details; see the file COPYING. If you cannot, write to the Free Software Foundation, 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org Copyright (C) 2006-2007 puck_lock with contributions from others; see the CREDITS file */ //==================================================================// #include "AtomicParsley.h" bool BOM_printed = false; uint32_t APar_ProvideTallyForAtom(const char *atom_name) { uint32_t tally_for_atom = 0; short iter = parsedAtoms[0].NextAtomNumber; while (true) { if (memcmp(parsedAtoms[iter].AtomicName, atom_name, 4) == 0) { if (parsedAtoms[iter].AtomicLength == 0) { tally_for_atom += file_size - parsedAtoms[iter].AtomicStart; } else if (parsedAtoms[iter].AtomicLength == 1) { tally_for_atom += parsedAtoms[iter].AtomicLengthExtended; } else { tally_for_atom += parsedAtoms[iter].AtomicLength; } } if (iter == 0) { break; } else { iter = parsedAtoms[iter].NextAtomNumber; } } return tally_for_atom; } void printBOM() { if (BOM_printed) return; #if defined(_WIN32) && !defined(__CYGWIN__) if (UnicodeOutputStatus == WIN32_UTF16) { APar_unicode_win32Printout(L"\xEF\xBB\xBF", "\xEF\xBB\xBF"); } #else fprintf(stdout, "\xEF\xBB\xBF"); // Default to output of a UTF-8 BOM #endif BOM_printed = true; return; } #if defined(_WIN32) && !defined(__CYGWIN__) void APar_unicode_win32Printout( wchar_t *unicode_out, char * utf8_out) { // based on // http://blogs.msdn.com/junfeng/archive/2004/02/25/79621.aspx // its possible that this isn't even available on windows95 DWORD dwBytesWritten; DWORD fdwMode; HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); // ThreadLocale adjustment, resource loading, etc. is skipped if ((GetFileType(outHandle) & FILE_TYPE_CHAR) && GetConsoleMode(outHandle, &fdwMode)) { if (wcsncmp(unicode_out, L"\xEF\xBB\xBF", 3) != 0) { // skip BOM when writing directly to the console WriteConsoleW( outHandle, unicode_out, wcslen(unicode_out), &dwBytesWritten, 0); } } else { // writing out to a file. Everything will be written out in utf8 to the // file. fprintf(stdout, "%s", utf8_out); } return; } #endif void APar_fprintf_UTF8_data(const char *utf8_encoded_data) { #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { fprintf(stdout, "%s", utf8_encoded_data); // just printout the raw utf8 bytes (not // characters) under pre-NT windows } else { wchar_t *utf16_data = Convert_multibyteUTF8_to_wchar(utf8_encoded_data); fflush(stdout); APar_unicode_win32Printout(utf16_data, (char *)utf8_encoded_data); fflush(stdout); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_encoded_data); #endif return; } void APar_Mark_UserData_area(uint8_t track_num, short userdata_atom, bool quantum_listing) { if (quantum_listing && track_num > 0) { fprintf(stdout, "User data; level: track=%u; atom \"%s\" ", track_num, parsedAtoms[userdata_atom].AtomicName); } else if (quantum_listing && track_num == 0) { fprintf(stdout, "User data; level: movie; atom \"%s\" ", parsedAtoms[userdata_atom].AtomicName); } else { fprintf(stdout, "User data \"%s\" ", parsedAtoms[userdata_atom].AtomicName); } return; } // the difference between APar_PrintUnicodeAssest above and // APar_SimplePrintUnicodeAssest below is: APar_PrintUnicodeAssest contains the // entire contents of the atom, NULL bytes and all APar_SimplePrintUnicodeAssest // contains a purely unicode string (either utf8 or utf16 with BOM) and slight // output formatting differences void APar_SimplePrintUnicodeAssest(char *unicode_string, int asset_length, bool print_encoding) { // 3gp files if (strncmp(unicode_string, "\xFE\xFF", 2) == 0) { // utf16 if (print_encoding) { fprintf(stdout, " (utf16): "); } unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, asset_length * 6, asset_length); #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { // pre-NT or AP-utf8.exe (pish, thats my win98se, // and without unicows support convert utf16toutf8 // and output raw bytes) unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, asset_length * 6, asset_length - 14); fprintf(stdout, "%s", utf8_data); free(utf8_data); utf8_data = NULL; } else { wchar_t *utf16_data = Convert_multibyteUTF16_to_wchar( unicode_string, asset_length / 2, true); // wchar_t* utf16_data = Convert_multibyteUTF16_to_wchar(unicode_string, // (asset_length / 2) + 1, true); APar_unicode_win32Printout(utf16_data, (char *)utf8_data); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_data); #endif free(utf8_data); utf8_data = NULL; } else { // utf8 if (print_encoding) { fprintf(stdout, " (utf8): "); } APar_fprintf_UTF8_data(unicode_string); } return; } /////////////////////////////////////////////////////////////////////////////////////// // embedded file extraction // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_Extract_uuid_binary_file uuid_atom - pointer to the struct holding the information describing the target atom originating_file - the full file path string to the parsed file output_path - a (possibly null) string where the embedded file will be extracted to If the output path is a null pointer, create a new path derived from originating file name & path - strip off the extension and use that as the base. Read into memory the contents of that particular uuid atom. Glob onto the base path the atom name and then the suffix that was embedded along with the file. Write out the file to the now fully formed uuid_outfile path. ----------------------*/ void APar_Extract_uuid_binary_file(AtomicInfo *uuid_atom, const char *originating_file, char *output_path) { uint32_t path_len = 0; uint64_t atom_offsets = 0; char *uuid_outfile = (char *)calloc( 1, sizeof(char) * MAXPATHLEN + 1); // malloc a new string because it may be a // cli arg for a specific output path if (output_path == NULL) { const char *orig_suffix = strrchr(originating_file, '.'); if (orig_suffix == NULL) { fprintf(stdout, "AP warning: a file extension for the input file was not " "found.\n\tGlobbing onto original filename...\n"); path_len = strlen(originating_file); memcpy(uuid_outfile, originating_file, path_len); } else { path_len = orig_suffix - originating_file; memcpy(uuid_outfile, originating_file, path_len); } } else { path_len = strlen(output_path); memcpy(uuid_outfile, output_path, path_len); } char *uuid_payload = (char *)calloc(1, sizeof(char) * (uuid_atom->AtomicLength - 36 + 1)); APar_readX(uuid_payload, source_file, uuid_atom->AtomicStart + 36, uuid_atom->AtomicLength - 36); uint32_t descrip_len = UInt32FromBigEndian(uuid_payload); atom_offsets += 4 + descrip_len; uint8_t suffix_len = (uint8_t)uuid_payload[atom_offsets]; char *file_suffix = (char *)calloc(1, sizeof(char) * suffix_len + 16); memcpy(file_suffix, uuid_payload + atom_offsets + 1, suffix_len); atom_offsets += 1 + suffix_len; uint8_t mime_len = (uint8_t)uuid_payload[atom_offsets]; uint64_t mimetype_string = atom_offsets + 1; atom_offsets += 1 + mime_len; uint64_t bin_len = UInt32FromBigEndian(uuid_payload + atom_offsets); atom_offsets += 4; sprintf(uuid_outfile + path_len, "-%s-uuid%s", uuid_atom->uuid_ap_atomname, file_suffix); FILE *outfile = APar_OpenFile(uuid_outfile, "wb"); if (outfile != NULL) { fwrite(uuid_payload + atom_offsets, (size_t)bin_len, 1, outfile); fclose(outfile); fprintf(stdout, "Extracted uuid=%s attachment (mime-type=%s) to file: ", uuid_atom->uuid_ap_atomname, uuid_payload + mimetype_string); APar_fprintf_UTF8_data(uuid_outfile); fprintf(stdout, "\n"); } free(uuid_payload); uuid_payload = NULL; free(uuid_outfile); uuid_outfile = NULL; free(file_suffix); file_suffix = NULL; return; } void APar_ExtractAAC_Artwork(short this_atom_num, char *pic_output_path, short artwork_count) { char *base_outpath = (char *)malloc(sizeof(char) * MAXPATHLEN + 1); if (snprintf(base_outpath, MAXPATHLEN + 1, "%s_artwork_%d", pic_output_path, artwork_count) > MAXPATHLEN) { free(base_outpath); return; } char *art_payload = (char *)malloc( sizeof(char) * (parsedAtoms[this_atom_num].AtomicLength - 16) + 1); memset(art_payload, 0, (parsedAtoms[this_atom_num].AtomicLength - 16) + 1); APar_readX(art_payload, source_file, parsedAtoms[this_atom_num].AtomicStart + 16, parsedAtoms[this_atom_num].AtomicLength - 16); char *suffix = (char *)malloc(sizeof(char) * 5); memset(suffix, 0, sizeof(char) * 5); if (memcmp(art_payload, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0) { strcpy(suffix, ".png"); } else if (memcmp(art_payload, "\xFF\xD8\xFF", 3) == 0) { strcpy(suffix, ".jpg"); } strcat(base_outpath, suffix); FILE *outfile = APar_OpenFile(base_outpath, "wb"); if (outfile != NULL) { fwrite(art_payload, (size_t)(parsedAtoms[this_atom_num].AtomicLength - 16), 1, outfile); fclose(outfile); fprintf(stdout, "Extracted artwork to file: "); APar_fprintf_UTF8_data(base_outpath); fprintf(stdout, "\n"); } free(base_outpath); free(art_payload); free(suffix); return; } /*---------------------- APar_ImageExtractTest buffer - pointer to raw image data id3args - *currently unused* when testing raw image data from an image file, results like mimetype & imagetype will be placed here Loop through the ImageList array and see if the first few bytes in the image data in buffer match any of the known image_binaryheader types listed. If it does, and its png, do a further test to see if its type 0x01 which requires it to be 32x32 ----------------------*/ ImageFileFormatDefinition *APar_ImageExtractTest(char *buffer, AdjunctArgs *id3args) { ImageFileFormatDefinition *thisImage = NULL; uint8_t total_image_tests = ImageListMembers(); for (uint8_t itest = 0; itest < total_image_tests; itest++) { if (ImageList[itest].image_testbytes == 0) { if (id3args != NULL) { id3args->mimeArg = ImageList[itest].image_mimetype; } return &ImageList[itest]; } else if (memcmp(buffer, ImageList[itest].image_binaryheader, ImageList[itest].image_testbytes) == 0) { if (id3args != NULL) { id3args->mimeArg = ImageList[itest].image_mimetype; if (id3args->pictype_uint8 == 0x01) { if (memcmp(buffer + 16, "\x00\x00\x00\x20\x00\x00\x00\x20", 8) != 0 && itest != 2) { id3args->pictype_uint8 = 0x02; } } } thisImage = &ImageList[itest]; break; } } return thisImage; } /*---------------------- APar_Extract_ID3v2_file id32_atom - pointer to the AtomicInfo ID32 atom that contains this while ID3 tag (containing all the frames like APIC) frame_str - either APIC or GEOB originfile - the originating mpeg-4 file that contains the ID32 atom destination_folder - *currently not used* TODO: extract to this folder id3args - *currently not used* TODO: extract by mimetype or imagetype or description Extracts (all) files of a particular frame type (APIC or GEOB - GEOB is currently not implemented) out to a file next to the originating mpeg-4 file. First, match frame_str to get the internal frameID number for APIC/GEOB frame. Locate the .ext of the origin file, duplicate the path including the basename (excluding the extension. Loop through the linked list of ID3v2Frame and search for the internal frameID number. When an image is found, test the data that the image contains and determine file extension from the ImageFileFormatDefinition structure (containing some popular image format/extension definitions). In combination with the file extension, use the image description and image type to create the name of the output file. The image (which if was compressed on disc was expanded when read in) and simply write out its data (stored in the 5th member of the frame's field strings. ----------------------*/ void APar_Extract_ID3v2_file(AtomicInfo *id32_atom, const char *frame_str, const char *originfile, const char *destination_folder, AdjunctArgs *id3args) { uint16_t iter = 0; ID3v2Frame *eval_frame = NULL; uint32_t basepath_len = 0; char *extract_filename = NULL; int frameID = MatchID3FrameIDstr( frame_str, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion); int frameType = KnownFrames[frameID + 1].ID3v2_FrameType; if (destination_folder == NULL) { basepath_len = (strrchr(originfile, '.') - originfile); } if (frameType == ID3_ATTACHED_PICTURE_FRAME || frameType == ID3_ATTACHED_OBJECT_FRAME) { if (id32_atom->ID32_TagInfo->ID3v2_FirstFrame == NULL) return; eval_frame = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; extract_filename = (char *)malloc(sizeof(char *) * MAXPATHLEN + 1); while (eval_frame != NULL) { if (frameType == eval_frame->ID3v2_FrameType) { memset(extract_filename, 0, sizeof(char *) * MAXPATHLEN + 1); memcpy(extract_filename, originfile, basepath_len); iter++; if (eval_frame->ID3v2_FrameType == ID3_ATTACHED_PICTURE_FRAME) { ImageFileFormatDefinition *thisimage = APar_ImageExtractTest( (eval_frame->ID3v2_Frame_Fields + 4)->field_string, NULL); char *img_description = APar_ConvertField_to_UTF8(eval_frame, ID3_DESCRIPTION_FIELD); sprintf( extract_filename + basepath_len, "-img#%u-(desc=%s)-0x%02X%s", iter, img_description, (uint8_t)((eval_frame->ID3v2_Frame_Fields + 2)->field_string[0]), thisimage->image_fileextn); if (img_description != NULL) { free(img_description); img_description = NULL; } } else { char *obj_description = APar_ConvertField_to_UTF8(eval_frame, ID3_DESCRIPTION_FIELD); char *obj_filename = APar_ConvertField_to_UTF8(eval_frame, ID3_FILENAME_FIELD); sprintf(extract_filename + basepath_len, "-obj#%u-(desc=%s)-%s", iter, obj_description, obj_filename); if (obj_description != NULL) { free(obj_description); obj_description = NULL; } if (obj_filename != NULL) { free(obj_filename); obj_filename = NULL; } } FILE *extractfile = APar_OpenFile(extract_filename, "wb"); if (extractfile != NULL) { fwrite((eval_frame->ID3v2_Frame_Fields + 4)->field_string, (size_t)((eval_frame->ID3v2_Frame_Fields + 4)->field_length), 1, extractfile); fclose(extractfile); fprintf( stdout, "Extracted %s to file: %s\n", (frameType == ID3_ATTACHED_PICTURE_FRAME ? "artwork" : "object"), extract_filename); } } eval_frame = eval_frame->ID3v2_NextFrame; } } if (extract_filename != NULL) { free(extract_filename); extract_filename = NULL; } return; } /////////////////////////////////////////////////////////////////////////////////////// // iTunes-style metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_ExtractDataAtom(int this_atom_number) { if (source_file != NULL) { AtomicInfo *thisAtom = &parsedAtoms[this_atom_number]; char *parent_atom_name; AtomicInfo parent_atom_stats = parsedAtoms[this_atom_number - 1]; parent_atom_name = parent_atom_stats.AtomicName; uint32_t min_atom_datasize = 12; uint32_t atom_header_size = 16; if (thisAtom->AtomicClassification == EXTENDED_ATOM) { if (thisAtom->uuid_style == UUID_DEPRECATED_FORM) { min_atom_datasize += 4; atom_header_size += 4; } else { min_atom_datasize = 36; atom_header_size = 36; } } if (thisAtom->AtomicLength > min_atom_datasize) { char *data_payload = (char *)malloc( sizeof(char) * (thisAtom->AtomicLength - atom_header_size + 1)); memset(data_payload, 0, sizeof(char) * (thisAtom->AtomicLength - atom_header_size + 1)); APar_readX(data_payload, source_file, thisAtom->AtomicStart + atom_header_size, thisAtom->AtomicLength - atom_header_size); if (thisAtom->AtomicVerFlags == AtomFlags_Data_Text) { if (thisAtom->AtomicLength < (atom_header_size + 4)) { // tvnn was showing up with 4 chars instead of 3; easier to null it // out for now data_payload[thisAtom->AtomicLength - atom_header_size] = '\00'; } APar_fprintf_UTF8_data(data_payload); fprintf(stdout, "\n"); } else { if ((memcmp(parent_atom_name, "trkn", 4) == 0) || (memcmp(parent_atom_name, "disk", 4) == 0)) { if (UInt16FromBigEndian(data_payload + 4) != 0) { fprintf(stdout, "%u of %u\n", UInt16FromBigEndian(data_payload + 2), UInt16FromBigEndian(data_payload + 4)); } else { fprintf(stdout, "%u\n", UInt16FromBigEndian(data_payload + 2)); } } else if (strncmp(parent_atom_name, "gnre", 4) == 0) { if (thisAtom->AtomicLength - atom_header_size < 3) { // oh, a 1byte int for genre number char *genre_string = GenreIntToString(UInt16FromBigEndian(data_payload)); if (genre_string != NULL) { fprintf(stdout, "%s\n", genre_string); } else { fprintf(stdout, " out of bound value - %u\n", UInt16FromBigEndian(data_payload)); } } else { fprintf(stdout, " out of bound value - %u\n", UInt16FromBigEndian(data_payload)); } } else if ((strncmp(parent_atom_name, "purl", 4) == 0) || (strncmp(parent_atom_name, "egid", 4) == 0)) { fprintf(stdout, "%s\n", data_payload); } else { if (thisAtom->AtomicVerFlags == AtomFlags_Data_UInt && (thisAtom->AtomicLength <= 20 || thisAtom->AtomicLength == 24)) { uint8_t bytes_rep = (uint8_t)(thisAtom->AtomicLength - atom_header_size); switch (bytes_rep) { case 1: { if ((memcmp(parent_atom_name, "cpil", 4) == 0) || (memcmp(parent_atom_name, "pcst", 4) == 0) || (memcmp(parent_atom_name, "pgap", 4) == 0)) { if (data_payload[0] == 1) { fprintf(stdout, "true\n"); } else { fprintf(stdout, "false\n"); } } else if (strncmp(parent_atom_name, "stik", 4) == 0) { stiks *returned_stik = MatchStikNumber((uint8_t)data_payload[0]); if (returned_stik != NULL) { fprintf(stdout, "%s\n", returned_stik->stik_string); } else { fprintf( stdout, "Unknown value: %u\n", (uint8_t)data_payload[0]); } } else if (strncmp(parent_atom_name, "rtng", 4) == 0) { // okay, this is definitely an 8-bit number if (data_payload[0] == 2) { fprintf(stdout, "Clean Content\n"); } else if (data_payload[0] != 0) { fprintf(stdout, "Explicit Content\n"); } else { fprintf(stdout, "Inoffensive\n"); } } else { fprintf(stdout, "%u\n", (uint8_t)data_payload[0]); } break; } case 2: { // tmpo fprintf(stdout, "%u\n", UInt16FromBigEndian(data_payload)); break; } case 4: { // tves, tvsn if (memcmp(parent_atom_name, "sfID", 4) == 0) { sfIDs *this_store = MatchStoreFrontNumber(UInt32FromBigEndian(data_payload)); if (this_store != NULL) { fprintf(stdout, "%s (%" PRIu32 ")\n", this_store->storefront_string, this_store->storefront_number); } else { fprintf(stdout, "Unknown (%" PRIu32 ")\n", UInt32FromBigEndian(data_payload)); } } else { fprintf( stdout, "%" PRIu32 "\n", UInt32FromBigEndian(data_payload)); } break; } case 8: { fprintf( stdout, "%" PRIu64 "\n", UInt64FromBigEndian(data_payload)); break; } } } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->AtomicVerFlags == AtomFlags_Data_uuid_binary && thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { uint64_t offset_into_uuiddata = 0; uint64_t descrip_len = UInt32FromBigEndian(data_payload); offset_into_uuiddata += 4; char *uuid_description = (char *)calloc(1, sizeof(char) * descrip_len + 16); // char uuid_description[descrip_len+1]; memcpy(uuid_description, data_payload + offset_into_uuiddata, descrip_len); offset_into_uuiddata += descrip_len; uint8_t suffix_len = (uint8_t)data_payload[offset_into_uuiddata]; offset_into_uuiddata += 1; char *file_suffix = (char *)calloc(1, sizeof(char) * suffix_len + 16); // char file_suffix[suffix_len+1]; memcpy( file_suffix, data_payload + offset_into_uuiddata, suffix_len); offset_into_uuiddata += suffix_len; uint8_t mime_len = (uint8_t)data_payload[offset_into_uuiddata]; offset_into_uuiddata += 1; char *uuid_mimetype = (char *)calloc(1, sizeof(char) * mime_len + 16); // char uuid_mimetype[mime_len+1]; memcpy( uuid_mimetype, data_payload + offset_into_uuiddata, mime_len); fprintf(stdout, "FILE%s; mime-type=%s; description=%s\n", file_suffix, uuid_mimetype, uuid_description); free(uuid_description); uuid_description = NULL; free(file_suffix); file_suffix = NULL; free(uuid_mimetype); uuid_description = NULL; } else { // purl & egid would end up here too, but Apple switched it // to a text string (0x00), so gets taken care above // explicitly fprintf(stdout, "hex 0x"); for (int hexx = 1; hexx <= (int)(thisAtom->AtomicLength - atom_header_size); ++hexx) { fprintf(stdout, "%02X", (uint8_t)data_payload[hexx - 1]); if ((hexx % 4) == 0 && hexx >= 4) { fprintf(stdout, " "); } if ((hexx % 16) == 0 && hexx > 16) { fprintf(stdout, "\n\t\t\t"); } if (hexx == (int)(thisAtom->AtomicLength - atom_header_size)) { fprintf(stdout, "\n"); } } } // end if AtomFlags_Data_UInt } free(data_payload); data_payload = NULL; } } } return; } void APar_Print_iTunesData(const char *path, char *output_path, uint8_t supplemental_info, uint8_t target_information, AtomicInfo *ilstAtom) { printBOM(); short artwork_count = 0; if (ilstAtom == NULL) { ilstAtom = APar_FindAtom("moov.udta.meta.ilst", false, SIMPLE_ATOM, 0); if (ilstAtom == NULL) return; } for (int i = ilstAtom->AtomicNumber; i < atom_number; i++) { AtomicInfo *thisAtom = &parsedAtoms[i]; if (strncmp(thisAtom->AtomicName, "data", 4) == 0) { // thisAtom->AtomicClassification == VERSIONED_ATOM) { AtomicInfo *parent = &parsedAtoms[APar_FindParentAtom(i, thisAtom->AtomicLevel)]; if ((thisAtom->AtomicVerFlags == AtomFlags_Data_Binary || thisAtom->AtomicVerFlags == AtomFlags_Data_Text || thisAtom->AtomicVerFlags == AtomFlags_Data_UInt) && target_information == PRINT_DATA) { if (strncmp(parent->AtomicName, "----", 4) == 0) { if (memcmp(parsedAtoms[parent->AtomicNumber + 2].AtomicName, "name", 4) == 0) { fprintf(stdout, "Atom \"%s\" [%s;%s] contains: ", parent->AtomicName, parsedAtoms[parent->AtomicNumber + 1].ReverseDNSdomain, parsedAtoms[parent->AtomicNumber + 2].ReverseDNSname); APar_ExtractDataAtom(i); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { // libmp4v2 doesn't properly set artwork with the right // flags (its all 0x00) artwork_count++; } else { // converts iso8859 © in '©ART' to a 2byte utf8 © glyph; replaces // libiconv conversion memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)parent->AtomicName, 4); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "Atom \""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\" contains: "); } else { fprintf(stdout, "Atom \"%s\" contains: ", twenty_byte_buffer); } APar_ExtractDataAtom(i); } } else if (memcmp(parent->AtomicName, "covr", 4) == 0) { artwork_count++; if (target_information == EXTRACT_ARTWORK) { APar_ExtractAAC_Artwork( thisAtom->AtomicNumber, output_path, artwork_count); } } if (thisAtom->AtomicLength <= 12) { fprintf( stdout, "\n"); // (corrupted atom); libmp4v2 touching a file with copyright } } } if (artwork_count != 0 && target_information == PRINT_DATA) { if (artwork_count == 1) { fprintf(stdout, "Atom \"covr\" contains: %i piece of artwork\n", artwork_count); } else { fprintf(stdout, "Atom \"covr\" contains: %i pieces of artwork\n", artwork_count); } } if (supplemental_info) { fprintf(stdout, "---------------------------\n"); dynUpd.updage_by_padding = false; // APar_DetermineDynamicUpdate(true); //gets the size of the padding APar_Optimize( true); // just to know if 'free' atoms can be considered padding, or (in // the case of say a faac file) it's *just* 'free' if (supplemental_info & 0x02) { // PRINT_FREE_SPACE fprintf(stdout, "free atom space: %" PRIu32 "\n", APar_ProvideTallyForAtom("free")); } if (supplemental_info & 0x04) { // PRINT_PADDING_SPACE if (!moov_atom_was_mooved) { fprintf(stdout, "padding available: %" PRIu64 " bytes\n", dynUpd.padding_bytes); } else { fprintf(stdout, "padding available: 0 (reorg)\n"); } } if (supplemental_info & 0x08 && dynUpd.moov_udta_atom != NULL) { // PRINT_USER_DATA_SPACE fprintf(stdout, "user data space: %" PRIu64 "\n", dynUpd.moov_udta_atom->AtomicLength); } if (supplemental_info & 0x10) { // PRINT_USER_DATA_SPACE fprintf(stdout, "media data space: %" PRIu32 "\n", APar_ProvideTallyForAtom("mdat")); } } return; } /////////////////////////////////////////////////////////////////////////////////////// // AP uuid metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_APuuidv5_contents(AtomicInfo *thisAtom) { memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->uuid_ap_atomname, 4); fprintf(stdout, "Atom uuid="); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, " (AP uuid for \""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\") contains: "); APar_ExtractDataAtom(thisAtom->AtomicNumber); return; } void APar_Print_APuuid_deprecated_contents(AtomicInfo *thisAtom) { memset(twenty_byte_buffer, 0, sizeof(char) * 20); isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->AtomicName, 4); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "Atom uuid=\""); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, "\" contains: "); } else { fprintf(stdout, "Atom uuid=\"%s\" contains: ", twenty_byte_buffer); } APar_ExtractDataAtom(thisAtom->AtomicNumber); return; } void APar_Print_APuuid_atoms(const char *path, char *output_path, uint8_t target_information) { AtomicInfo *thisAtom = NULL; printBOM(); AtomicInfo *metaAtom = APar_FindAtom("moov.udta.meta", false, VERSIONED_ATOM, 0); if (metaAtom == NULL) return; for (int i = metaAtom->NextAtomNumber; i < atom_number; i++) { thisAtom = &parsedAtoms[i]; if (thisAtom->AtomicLevel <= metaAtom->AtomicLevel) break; // we've gone too far if (thisAtom->AtomicClassification == EXTENDED_ATOM) { if (thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { if (target_information == PRINT_DATA) APar_Print_APuuidv5_contents(thisAtom); if (target_information == EXTRACT_ALL_UUID_BINARYS && thisAtom->AtomicVerFlags == AtomFlags_Data_uuid_binary) { APar_Extract_uuid_binary_file(thisAtom, path, output_path); } } if (thisAtom->uuid_style == UUID_DEPRECATED_FORM && target_information == PRINT_DATA) APar_Print_APuuid_deprecated_contents(thisAtom); } } return; } /////////////////////////////////////////////////////////////////////////////////////// // 3GP asset metadata listings // /////////////////////////////////////////////////////////////////////////////////////// void APar_PrintUnicodeAssest(char *unicode_string, int asset_length) { // 3gp files if (strncmp(unicode_string, "\xFE\xFF", 2) == 0) { // utf16 fprintf(stdout, " (utf16)] : "); unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, (asset_length - 13) * 6, asset_length - 14); #if defined(_WIN32) && !defined(__CYGWIN__) if (GetVersion() & 0x80000000 || UnicodeOutputStatus == UNIVERSAL_UTF8) { // pre-NT or AP-utf8.exe (pish, thats my win98se, // and without unicows support convert utf16toutf8 // and output raw bytes) unsigned char *utf8_data = Convert_multibyteUTF16_to_UTF8( unicode_string, (asset_length - 13) * 6, asset_length - 14); fprintf(stdout, "%s", utf8_data); free(utf8_data); utf8_data = NULL; } else { wchar_t *utf16_data = Convert_multibyteUTF16_to_wchar( unicode_string, (asset_length - 16) / 2, true); APar_unicode_win32Printout(utf16_data, (char *)utf8_data); free(utf16_data); utf16_data = NULL; } #else fprintf(stdout, "%s", utf8_data); #endif free(utf8_data); utf8_data = NULL; } else { // utf8 fprintf(stdout, " (utf8)] : "); APar_fprintf_UTF8_data(unicode_string); } return; } void APar_Print_single_userdata_atomcontents(uint8_t track_num, short userdata_atom, bool quantum_listing) { uint32_t box = UInt32FromBigEndian(parsedAtoms[userdata_atom].AtomicName); char bitpacked_lang[3]; memset(bitpacked_lang, 0, 3); unsigned char unpacked_lang[3]; uint32_t box_length = parsedAtoms[userdata_atom].AtomicLength; char *box_data = (char *)malloc(sizeof(char) * box_length); memset(box_data, 0, sizeof(char) * box_length); switch (box) { case 0x7469746C: //'titl' case 0x64736370: //'dscp' case 0x63707274: //'cprt' case 0x70657266: //'perf' case 0x61757468: //'auth' case 0x676E7265: //'gnre' case 0x616C626D: //'albm' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 12); APar_UnpackLanguage(unpacked_lang, packed_lang); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 14, box_length - 14); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang // get tracknumber *after* we read the whole tag; if we have a utf16 tag, it // will have a BOM, indicating if we have to search for 2 NULLs or a utf8 // single NULL, then the ****optional**** tracknumber uint16_t track_num = 1000; // tracknum is a uint8_t, so setting it > 256 // means a number wasn't found if (box == 0x616C626D) { //'albm' has an *optional* uint8_t at the end for // tracknumber; if the last byte in the tag is not // 0, then it must be the optional tracknum (or a // non-compliant, non-NULL-terminated string). This // byte is the length - (14 bytes +1tracknum) or -15 if (box_data[box_length - 15] != 0) { track_num = (uint16_t)box_data[box_length - 15]; box_data[box_length - 15] = 0; // NULL out the last byte if found to be not 0 - it will impact // unicode conversion if it remains } } fprintf(stdout, "[lang=%s", unpacked_lang); APar_PrintUnicodeAssest(box_data, box_length); if (box == 0x616C626D && track_num != 1000) { fprintf(stdout, " | Track: %u", track_num); } fprintf(stdout, "\n"); break; } case 0x72746E67: //'rtng' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 12, 4); fprintf(stdout, "[Rating Entity=%s", box_data); memset(box_data, 0, box_length); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 16, 4); fprintf(stdout, " | Criteria=%s", box_data); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 20); APar_UnpackLanguage(unpacked_lang, packed_lang); fprintf(stdout, " lang=%s", unpacked_lang); memset(box_data, 0, box_length); APar_readX(box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 22, box_length - 8); APar_PrintUnicodeAssest(box_data, box_length - 8); fprintf(stdout, "\n"); break; } case 0x636C7366: //'clsf' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 12, box_length - 12); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang fprintf(stdout, "[Classification Entity=%s", box_data); fprintf(stdout, " | Index=%u", UInt16FromBigEndian(box_data + 4)); uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 18); APar_UnpackLanguage(unpacked_lang, packed_lang); fprintf(stdout, " lang=%s", unpacked_lang); APar_PrintUnicodeAssest(box_data + 8, box_length - 8); fprintf(stdout, "\n"); break; } case 0x6B797764: //'kywd' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint64_t box_offset = 12; uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset += 2; APar_UnpackLanguage(unpacked_lang, packed_lang); uint8_t keyword_count = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; fprintf(stdout, "[Keyword count=%u", keyword_count); fprintf(stdout, " lang=%s]", unpacked_lang); char *keyword_data = (char *)malloc(sizeof(char) * box_length * 2); for (uint8_t x = 1; x <= keyword_count; x++) { memset(keyword_data, 0, box_length * 2); uint8_t keyword_length = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; APar_readX(keyword_data, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset, keyword_length); box_offset += keyword_length; APar_SimplePrintUnicodeAssest(keyword_data, keyword_length, true); } free(keyword_data); keyword_data = NULL; fprintf(stdout, "\n"); break; } case 0x6C6F6369: //'loci' aka The Most Heinous Metadata Atom Every Invented - // decimal meters? fictional location? Astromical Body? Say I // shoot it on the International Space Station? That isn't a // Astronimical Body. And 16.16 alt only goes up to 20.3 // miles (because of negatives, its really 15.15) & the ISS // is // at 230 miles. Oh, pish.... what ever shall I do? I fear I // am on the horns of a dilema. { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint64_t box_offset = 12; uint16_t packed_lang = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset += 2; APar_UnpackLanguage(unpacked_lang, packed_lang); APar_readX(box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset, box_length); fprintf(stdout, "[lang=%s] ", unpacked_lang); // the length of the location string is unknown (max is box lenth), but the // long/lat/alt/body/notes needs to be retrieved. test if the location // string is utf16; if so search for 0x0000 (or if utf8, find the first // NULL). if (strncmp(box_data, "\xFE\xFF", 2) == 0) { box_offset += 2 * widechar_len(box_data, box_length) + 2; //*2 for utf16 (double-byte); +2 for the terminating NULL fprintf(stdout, "(utf16) "); } else { fprintf(stdout, "(utf8) "); box_offset += strlen(box_data) + 1; //+1 for the terminating NULL } fprintf(stdout, "Location: "); APar_SimplePrintUnicodeAssest(box_data, box_length, false); uint8_t location_role = APar_read8( source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset); box_offset++; switch (location_role) { case 0: { fprintf(stdout, " (Role: shooting location) "); break; } case 1: { fprintf(stdout, " (Role: real location) "); break; } case 2: { fprintf(stdout, " (Role: fictional location) "); break; } default: { fprintf(stdout, " (Role: [reserved]) "); break; } } char *float_buffer = (char *)malloc(sizeof(char) * 5); memset(float_buffer, 0, 5); fprintf(stdout, "[Long %lf", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; fprintf(stdout, " Lat %lf", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; fprintf(stdout, " Alt %lf ", fixed_point_16x16bit_to_double(APar_read32( float_buffer, source_file, parsedAtoms[userdata_atom].AtomicStart + box_offset))); box_offset += 4; free(float_buffer); float_buffer = NULL; if (box_offset < box_length) { fprintf(stdout, " Body: "); APar_SimplePrintUnicodeAssest( box_data + box_offset - 14, box_length - box_offset, false); if (strncmp(box_data + box_offset - 14, "\xFE\xFF", 2) == 0) { box_offset += 2 * widechar_len(box_data + box_offset - 14, box_length - box_offset) + 2; //*2 for utf16 (double-byte); +2 for the terminating NULL } else { box_offset += strlen(box_data + box_offset - 14) + 1; //+1 for the terminating NULL } } fprintf(stdout, "]"); if (box_offset < box_length) { fprintf(stdout, " Notes: "); APar_SimplePrintUnicodeAssest( box_data + box_offset - 14, box_length - box_offset, false); } fprintf(stdout, "\n"); break; } case 0x79727263: //'yrrc' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); uint16_t recording_year = APar_read16(bitpacked_lang, source_file, parsedAtoms[userdata_atom].AtomicStart + 12); fprintf(stdout, ": %u\n", recording_year); break; } case 0x6E616D65: //'name' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_fprintf_UTF8_data(": "); APar_readX( box_data, source_file, parsedAtoms[userdata_atom].AtomicStart + 8, box_length - 8); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang APar_fprintf_UTF8_data(box_data); APar_fprintf_UTF8_data("\n"); break; } case 0x686E7469: //'hnti' { APar_Mark_UserData_area(track_num, userdata_atom, quantum_listing); APar_readX( box_data, source_file, parsedAtoms[userdata_atom + 1].AtomicStart + 8, box_length - 8); // 4bytes length, 4 bytes name, 4 bytes flags, 2 bytes lang fprintf(stdout, "for %s:\n", parsedAtoms[userdata_atom + 1].AtomicName); APar_fprintf_UTF8_data(box_data); break; } default: { break; } } return; } /////////////////////////////////////////////////////////////////////////////////////// // id3 displaying functions // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_ID3TextField(ID3v2Frame *textframe, ID3v2Fields *textfield, bool linefeed = false) { // this won't accommodate id3v2.4's multiple strings separated by NULLs if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_LATIN1) { // all frames that have text encodings have the encoding as // the first field if (textfield->field_length > 0) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 4) + 2); isolat1ToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string, textfield->field_length); fprintf(stdout, "%s", conv_buffer); free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16LE_WITH_BOM) { // technically AP *writes* uff16LE here, but // based on BOM, it could be utf16BE if (textfield->field_length > 2) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 2) + 2); if (strncmp(textfield->field_string, "\xFF\xFE", 2) == 0) { UTF16LEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string + 2, textfield->field_length); fprintf(stdout, "%s", conv_buffer); } else { UTF16BEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string + 2, textfield->field_length); fprintf(stdout, "%s", conv_buffer); } free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16BE_NO_BOM) { if (textfield->field_length > 0) { char *conv_buffer = (char *)calloc(1, sizeof(char *) * (textfield->field_length * 2) + 2); UTF16BEToUTF8((unsigned char *)conv_buffer, sizeof(char *) * (textfield->field_length * 4) + 2, (unsigned char *)textfield->field_string, textfield->field_length); fprintf(stdout, "%s", conv_buffer); free(conv_buffer); conv_buffer = NULL; } } else if (textframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF8) { fprintf(stdout, "%s", textfield->field_string); } else { fprintf(stdout, "(unknown type: 0x%X", (uint8_t)textframe->ID3v2_Frame_Fields->field_string[0]); } if (linefeed) fprintf(stdout, "\n"); return; } const char *APar_GetTextEncoding(ID3v2Frame *aframe, ID3v2Fields *textfield) { const char *text_encoding = NULL; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_LATIN1) text_encoding = "latin1"; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16BE_NO_BOM) { if (strncmp(textfield->field_string, "\xFF\xFE", 2) == 0) { text_encoding = "utf16le"; } else if (strncmp(textfield->field_string, "\xFE\xFF", 2) == 0) { text_encoding = "utf16be"; } } if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF16LE_WITH_BOM) text_encoding = "utf16le"; if (aframe->ID3v2_Frame_Fields->field_string[0] == TE_UTF8) text_encoding = "utf8"; return text_encoding; } void APar_Print_ID3v2_tags(AtomicInfo *id32_atom) { // TODO properly printout latin1 for fields like owner // TODO for binary fields (like GRID group data) scan through to see if it // needs to be printed in hex fprintf(stdout, "Maj.Min.Rev version // was 2.%u.%u\n", id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, // id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion); char *id32_level = (char *)calloc(1, sizeof(char *) * 16); if (id32_atom->AtomicLevel == 2) { memcpy(id32_level, "file level", 10); } else if (id32_atom->AtomicLevel == 3) { memcpy(id32_level, "movie level", 11); } else if (id32_atom->AtomicLevel == 4) { sprintf(id32_level, "track #%u", 1); // unimplemented; need to pass a variable here } unsigned char unpacked_lang[3]; APar_UnpackLanguage(unpacked_lang, id32_atom->AtomicLanguage); if (id32_atom->ID32_TagInfo->ID3v2_FirstFrame != NULL) { fprintf(stdout, "ID32 atom [lang=%s] at %s contains an ID3v2.%u.%u tag (%u tags, " "%u bytes):\n", unpacked_lang, id32_level, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion, id32_atom->ID32_TagInfo->ID3v2_FrameCount, id32_atom->ID32_TagInfo->ID3v2Tag_Length); } else { fprintf(stdout, "ID32 atom [lang=%s] at %s contains an ID3v2.%u.%u tag. ", unpacked_lang, id32_level, id32_atom->ID32_TagInfo->ID3v2Tag_MajorVersion, id32_atom->ID32_TagInfo->ID3v2Tag_RevisionVersion); if (ID3v2_TestTagFlag(id32_atom->ID32_TagInfo->ID3v2Tag_Flags, ID32_TAGFLAG_UNSYNCRONIZATION)) { fprintf(stdout, "Unsynchronized flag set. Unsupported. No tags read. %" PRIu32 " bytes.\n", id32_atom->ID32_TagInfo->ID3v2Tag_Length); } } ID3v2Frame *target_frameinfo = id32_atom->ID32_TagInfo->ID3v2_FirstFrame; while (target_frameinfo != NULL) { if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_GROUPING) && target_frameinfo && target_frameinfo->ID3v2_FrameType != ID3_GROUP_ID_FRAME) { fprintf(stdout, " Tag: %s GID=0x%02X \"%s\" ", target_frameinfo->ID3v2_Frame_Namestr, target_frameinfo->ID3v2_Frame_GroupingSymbol, KnownFrames[target_frameinfo->ID3v2_Frame_ID + 1] .ID3V2_FrameDescription); } else { fprintf(stdout, " Tag: %s \"%s\" ", target_frameinfo->ID3v2_Frame_Namestr, KnownFrames[target_frameinfo->ID3v2_Frame_ID + 1] .ID3V2_FrameDescription); } uint8_t frame_comp_idx = GetFrameCompositionDescription(target_frameinfo->ID3v2_FrameType); if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_UNKNOWN_FRAME) { fprintf(stdout, "(unknown frame) %" PRIu32 " bytes\n", target_frameinfo->ID3v2_Frame_Fields->field_length); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_TEXT_FRAME) { ID3v2Fields *atextfield = target_frameinfo->ID3v2_Frame_Fields + 1; if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "(%s) : { ", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); } else { fprintf(stdout, "(%s) : ", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); } while (true) { if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "\""); } if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_CONTENTTYPE) { char *genre_string = NULL; int genre_idx = (int)strtol(atextfield->field_string, &genre_string, 10); if (genre_string != atextfield->field_string) { genre_string = ID3GenreIntToString(genre_idx); if (target_frameinfo->textfield_tally == 1) { fprintf(stdout, "%s\n", ID3GenreIntToString(genre_idx)); } else { fprintf(stdout, "%s", ID3GenreIntToString(genre_idx)); } } else { APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } } else if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_COPYRIGHT) { APar_fprintf_UTF8_data("\xC2\xA9 "); APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } else if (target_frameinfo->ID3v2_Frame_ID == ID3v2_FRAME_PRODNOTICE) { APar_fprintf_UTF8_data("\xE2\x84\x97 "); APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } else { APar_Print_ID3TextField( target_frameinfo, atextfield, target_frameinfo->textfield_tally == 1 ? true : false); } if (target_frameinfo->textfield_tally > 1) { fprintf(stdout, "\""); } else { break; } atextfield = atextfield->next_field; if (atextfield == NULL) { fprintf(stdout, " }\n"); break; } else { fprintf(stdout, ", "); } } } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_TEXT_FRAME_USERDEF) { fprintf(stdout, "(user-defined text frame) "); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_URL_FRAME) { fprintf(stdout, "(url frame) : %s\n", (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_URL_FRAME_USERDEF) { fprintf(stdout, "(user-defined url frame) "); fprintf(stdout, "%u fields\n", target_frameinfo->ID3v2_FieldCount); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_UNIQUE_FILE_ID_FRAME) { if (test_limited_ascii( (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_length)) { fprintf(stdout, "(owner='%s') : %s\n", target_frameinfo->ID3v2_Frame_Fields->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else { fprintf(stdout, "(owner='%s') : 0x", target_frameinfo->ID3v2_Frame_Fields->field_string); for (uint32_t hexidx = 0; hexidx < (target_frameinfo->ID3v2_Frame_Fields + 1)->field_length; hexidx++) { fprintf(stdout, "%02X", (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1) ->field_string[hexidx]); } fprintf(stdout, "\n"); } } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_CD_ID_FRAME) { // TODO: print hex representation uint8_t tracklistings = 0; if (target_frameinfo->ID3v2_Frame_Fields->field_length >= 16) { tracklistings = target_frameinfo->ID3v2_Frame_Fields->field_length / 8; fprintf(stdout, "(Music CD Identifier) : Entries for %u tracks + leadout " "track.\n Hex: 0x", tracklistings - 1); } else { fprintf(stdout, "(Music CD Identifier) : Unknown format (less then 16 " "bytes).\n Hex: 0x"); } for (uint16_t hexidx = 1; hexidx < target_frameinfo->ID3v2_Frame_Fields->field_length + 1; hexidx++) { fprintf(stdout, "%02X", (uint8_t)target_frameinfo->ID3v2_Frame_Fields ->field_string[hexidx - 1]); if (hexidx % 4 == 0) fprintf(stdout, " "); } fprintf(stdout, "\n"); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_DESCRIBED_TEXT_FRAME) { fprintf(stdout, "(%s, lang=%s, desc[", APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2), (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2); fprintf(stdout, "]) : "); APar_Print_ID3TextField( target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3, true); } else if (FrameTypeConstructionList[frame_comp_idx].ID3_FrameType == ID3_ATTACHED_PICTURE_FRAME) { fprintf( stdout, "(type=0x%02X-'%s', mimetype=%s, %s, desc[", (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 2)->field_string[0], ImageTypeList[(uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 2) ->field_string[0]] .imagetype_str, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string, APar_GetTextEncoding(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 1)); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3); if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { fprintf(stdout, "]) : %" PRIu32 " bytes (%" PRIu32 " compressed)\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %" PRIu32 " bytes\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length); } } else if (target_frameinfo->ID3v2_FrameType == ID3_ATTACHED_OBJECT_FRAME) { fprintf(stdout, "(filename="); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 2); fprintf(stdout, ", mimetype=%s, desc[", (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); APar_Print_ID3TextField(target_frameinfo, target_frameinfo->ID3v2_Frame_Fields + 3); if (ID3v2_TestFrameFlag(target_frameinfo->ID3v2_Frame_Flags, ID32_FRAMEFLAG_COMPRESSED)) { fprintf(stdout, "]) : %" PRIu32 " bytes (%" PRIu32 " compressed)\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length, target_frameinfo->ID3v2_Frame_Length); } else { fprintf(stdout, "]) : %" PRIu32 " bytes\n", (target_frameinfo->ID3v2_Frame_Fields + 4)->field_length); } } else if (target_frameinfo->ID3v2_FrameType == ID3_GROUP_ID_FRAME) { fprintf( stdout, "(owner='%s') : 0x%02X", target_frameinfo->ID3v2_Frame_Fields->field_string, (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1)->field_string[0]); if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 0) { fprintf(stdout, "; groupdata='%s'\n", (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string); } else { fprintf(stdout, "\n"); } } else if (target_frameinfo->ID3v2_FrameType == ID3_PRIVATE_FRAME) { fprintf(stdout, "(owner='%s') : %s\n", target_frameinfo->ID3v2_Frame_Fields->field_string, (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else if (target_frameinfo->ID3v2_FrameType == ID3_SIGNATURE_FRAME) { fprintf(stdout, "{GID=0x%02X) : %s\n", (uint8_t)target_frameinfo->ID3v2_Frame_Fields->field_string[0], (target_frameinfo->ID3v2_Frame_Fields + 1)->field_string); } else if (target_frameinfo->ID3v2_FrameType == ID3_PLAYCOUNTER_FRAME) { if (target_frameinfo->ID3v2_Frame_Fields->field_length == 4) { fprintf(stdout, ": %" PRIu32 "\n", syncsafe32_to_UInt32( target_frameinfo->ID3v2_Frame_Fields->field_string)); } else if (target_frameinfo->ID3v2_Frame_Fields->field_length > 4) { fprintf(stdout, ": %" PRIu64 "\n", syncsafeXX_to_UInt64( target_frameinfo->ID3v2_Frame_Fields->field_string, target_frameinfo->ID3v2_Frame_Fields->field_length)); } } else if (target_frameinfo->ID3v2_FrameType == ID3_POPULAR_FRAME) { fprintf( stdout, "(owner='%s') : %u", target_frameinfo->ID3v2_Frame_Fields->field_string, (uint8_t)(target_frameinfo->ID3v2_Frame_Fields + 1)->field_string[0]); if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 0) { if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length == 4) { fprintf( stdout, "; playcount=%" PRIu32 "\n", syncsafe32_to_UInt32( (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string)); } else if ((target_frameinfo->ID3v2_Frame_Fields + 2)->field_length > 4) { fprintf( stdout, "; playcount=%" PRIu64 "\n", syncsafeXX_to_UInt64( (target_frameinfo->ID3v2_Frame_Fields + 2)->field_string, (target_frameinfo->ID3v2_Frame_Fields + 2)->field_length)); } else { fprintf(stdout, "\n"); // don't know what it was supposed to be, so skip it } } else { fprintf(stdout, "\n"); } } else { fprintf(stdout, " [idx=%u;%d]\n", frame_comp_idx, FrameTypeConstructionList[frame_comp_idx].ID3_FrameType); } target_frameinfo = target_frameinfo->ID3v2_NextFrame; } free(id32_level); id32_level = NULL; return; } /////////////////////////////////////////////////////////////////////////////////////// // metadata scheme searches // /////////////////////////////////////////////////////////////////////////////////////// void APar_Print_metachild_atomcontents(uint8_t track_num, short metachild_atom, bool quantum_listing) { if (memcmp(parsedAtoms[metachild_atom].AtomicName, "ID32", 4) == 0) { APar_ID32_ScanID3Tag(source_file, &parsedAtoms[metachild_atom]); APar_Print_ID3v2_tags(&parsedAtoms[metachild_atom]); } return; } void APar_PrintMetaChildren(AtomicInfo *metaAtom, AtomicInfo *hdlrAtom, bool quantum_listing) { if (metaAtom != NULL && hdlrAtom != NULL) { if (hdlrAtom->ancillary_data == 0x49443332) { for (int i = metaAtom->NextAtomNumber; i < atom_number; i++) { if (parsedAtoms[i].AtomicLevel <= metaAtom->AtomicLevel) break; // we've gone too far if (parsedAtoms[i].AtomicLevel == metaAtom->AtomicLevel + 1) APar_Print_metachild_atomcontents(0, i, quantum_listing); } } } return; } void APar_PrintID32Metadata(bool quantum_listing) { uint8_t total_tracks = 0; uint8_t a_track = 0; AtomicInfo *metaAtom = NULL; AtomicInfo *metahandlerAtom = NULL; char trackmeta_atom_path[50]; printBOM(); // file level metaAtom = APar_FindAtom("meta", false, VERSIONED_ATOM, 0); metahandlerAtom = APar_FindAtom("meta.hdlr", false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); // movie level metaAtom = APar_FindAtom("moov.meta", false, VERSIONED_ATOM, 0); metahandlerAtom = APar_FindAtom("moov.meta.hdlr", false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); // track level APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. for (uint8_t i = 1; i <= total_tracks; i++) { memset(&trackmeta_atom_path, 0, 50); sprintf(trackmeta_atom_path, "moov.trak[%u].meta", i); metaAtom = APar_FindAtom(trackmeta_atom_path, false, VERSIONED_ATOM, 0); sprintf(trackmeta_atom_path, "moov.trak[%u].meta.hdlr", i); metahandlerAtom = APar_FindAtom(trackmeta_atom_path, false, VERSIONED_ATOM, 0); APar_PrintMetaChildren(metaAtom, metahandlerAtom, quantum_listing); } return; } /*---------------------- APar_Print_ISO_UserData_per_track quantum_listing - controls whether to simply print each asset, or preface each asset with "movie level" This will only show what is under moov.trak.udta atoms (not moov.udta). Get the total number of tracks; construct the moov.trak[index].udta path to find, then if the atom after udta is of a greater level, read in from the file & print out what it contains. ----------------------*/ void APar_PrintUserDataAssests(bool quantum_listing) { printBOM(); AtomicInfo *udtaAtom = APar_FindAtom("moov.udta", false, SIMPLE_ATOM, 0); if (udtaAtom != NULL) { for (int i = udtaAtom->NextAtomNumber; i < atom_number; i++) { if (parsedAtoms[i].AtomicLevel <= udtaAtom->AtomicLevel) break; // we've gone too far if (parsedAtoms[i].AtomicLevel == udtaAtom->AtomicLevel + 1) APar_Print_single_userdata_atomcontents(0, i, quantum_listing); } } APar_PrintID32Metadata(quantum_listing); APar_Print_APuuid_atoms(NULL, NULL, PRINT_DATA); return; } /*---------------------- APar_Print_ISO_UserData_per_track This will only show what is under moov.trak.udta atoms (not moov.udta). Get the total number of tracks; construct the moov.trak[index].udta path to find, then if the atom after udta is of a greater level, read in from the file & print out what it contains. ----------------------*/ void APar_Print_ISO_UserData_per_track() { uint8_t total_tracks = 0; uint8_t a_track = 0; // unused short a_trak_atom = 0; char iso_atom_path[400]; AtomicInfo *trak_udtaAtom = NULL; APar_FindAtomInTrack(total_tracks, a_track, NULL); // With track_num set to 0, it will return the // total trak atom into total_tracks here. for (uint8_t i = 1; i <= total_tracks; i++) { memset(&iso_atom_path, 0, 400); sprintf(iso_atom_path, "moov.trak[%u].udta", i); trak_udtaAtom = APar_FindAtom(iso_atom_path, false, SIMPLE_ATOM, 0); if (trak_udtaAtom != NULL && parsedAtoms[trak_udtaAtom->NextAtomNumber].AtomicLevel == trak_udtaAtom->AtomicLevel + 1) { a_trak_atom = trak_udtaAtom->NextAtomNumber; while ( parsedAtoms[a_trak_atom].AtomicLevel > trak_udtaAtom ->AtomicLevel) { // only work on moov.trak[i].udta's child atoms if (parsedAtoms[a_trak_atom].AtomicLevel == trak_udtaAtom->AtomicLevel + 1) APar_Print_single_userdata_atomcontents(i, a_trak_atom, true); a_trak_atom = parsedAtoms[a_trak_atom].NextAtomNumber; } } } APar_PrintUserDataAssests(true); return; } /////////////////////////////////////////////////////////////////////////////////////// // Atom Tree // /////////////////////////////////////////////////////////////////////////////////////// /*---------------------- APar_PrintAtomicTree Following the linked list (by NextAtomNumber), list each atom as they exist in the hieararchy, reflecting positions of moving, eliminating & additions. This listing can occur during the course of tagging as well to assist in diagnosing problems. ----------------------*/ void APar_PrintAtomicTree() { bool unknown_atom = false; char *tree_padding = (char *)malloc( sizeof(char) * 126); // for a 25-deep atom tree (4 spaces per atom)+single space+term. uint32_t freeSpace = 0; short thisAtomNumber = 0; printBOM(); // loop through each atom in the struct array (which holds the offset // info/data) while (true) { AtomicInfo *thisAtom = &parsedAtoms[thisAtomNumber]; memset(tree_padding, 0, sizeof(char) * 126); memset(twenty_byte_buffer, 0, sizeof(char) * 20); if (thisAtom->uuid_ap_atomname != NULL) { isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->uuid_ap_atomname, 4); // converts iso8859 © in '©ART' to a 2byte utf8 © glyph } else { isolat1ToUTF8((unsigned char *)twenty_byte_buffer, 10, (unsigned char *)thisAtom->AtomicName, 4); // converts iso8859 © in '©ART' to a 2byte utf8 © glyph } strcpy(tree_padding, ""); if (thisAtom->AtomicLevel != 1) { for (uint8_t pad = 1; pad < thisAtom->AtomicLevel; pad++) { strcat(tree_padding, " "); // if the atom depth is over 1, then add spaces before // text starts to form the tree } strcat(tree_padding, " "); // add a single space } if (thisAtom->AtomicLength == 0) { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 " (%" PRIu64 "*), ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, ((uint64_t)file_size - thisAtom->AtomicStart), thisAtom->AtomicLength, (uint64_t)file_size); fprintf(stdout, "\t\t\t (*)denotes length of atom goes to End-of-File\n"); } else if (thisAtom->AtomicLength == 1) { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 " (^), ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLengthExtended, (thisAtom->AtomicStart + thisAtom->AtomicLengthExtended)); fprintf(stdout, "\t\t\t (^)denotes a 64-bit atom length\n"); // uuid atoms of any sort } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->uuid_style == UUID_DEPRECATED_FORM) { if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom uuid=%s @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } } else if (thisAtom->AtomicClassification == EXTENDED_ATOM && thisAtom->uuid_style != UUID_DEPRECATED_FORM) { if (thisAtom->uuid_style == UUID_AP_SHA1_NAMESPACE) { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, "(APuuid=%s) @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom uuid=", tree_padding); APar_print_uuid((ap_uuid_t *)thisAtom->AtomicName, false); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } // 3gp assets (most of them anyway) } else if (thisAtom->AtomicClassification == PACKED_LANG_ATOM) { unsigned char unpacked_lang[3]; APar_UnpackLanguage(unpacked_lang, thisAtom->AtomicLanguage); if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom ", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " [%s] @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", unpacked_lang, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom %s [%s] @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64 "\n", tree_padding, twenty_byte_buffer, unpacked_lang, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } // all other atoms (the bulk of them will fall here) } else { if (UnicodeOutputStatus == WIN32_UTF16) { fprintf(stdout, "%sAtom ", tree_padding); APar_fprintf_UTF8_data(twenty_byte_buffer); fprintf(stdout, " @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } else { fprintf(stdout, "%sAtom %s @ %" PRIu64 " of size: %" PRIu64 ", ends @ %" PRIu64, tree_padding, twenty_byte_buffer, thisAtom->AtomicStart, thisAtom->AtomicLength, (thisAtom->AtomicStart + thisAtom->AtomicLength)); } if (thisAtom->AtomicContainerState == UNKNOWN_ATOM_TYPE) { for (uint8_t i = 0; i < (5 - thisAtom->AtomicLevel); i++) { fprintf(stdout, "\t"); } fprintf(stdout, "\t\t\t ~\n"); unknown_atom = true; } else { fprintf(stdout, "\n"); } } // simple tally & percentage of free space info if (memcmp(thisAtom->AtomicName, "free", 4) == 0) { freeSpace = freeSpace + thisAtom->AtomicLength; } // this is where the *raw* audio/video file is, the rest is // container-related fluff. if ((memcmp(thisAtom->AtomicName, "mdat", 4) == 0) && (thisAtom->AtomicLength > 100)) { mdatData += thisAtom->AtomicLength; } else if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && thisAtom->AtomicLength == 0) { // mdat.length = 0 = ends at EOF mdatData = file_size - thisAtom->AtomicStart; } else if (memcmp(thisAtom->AtomicName, "mdat", 4) == 0 && thisAtom->AtomicLengthExtended != 0) { mdatData += thisAtom->AtomicLengthExtended; // this is still adding a (limited) // uint64_t into a uint32_t } if (parsedAtoms[thisAtomNumber].NextAtomNumber == 0) { break; } else { thisAtomNumber = parsedAtoms[thisAtomNumber].NextAtomNumber; } } if (unknown_atom) { fprintf(stdout, "\n ~ denotes an unknown atom\n"); } fprintf(stdout, "------------------------------------------------------\n"); fprintf(stdout, "Total size: %" PRIu64 " bytes; ", (uint64_t)file_size); fprintf(stdout, "%i atoms total.\n", atom_number - 1); fprintf(stdout, "Media data: %" PRIu64 " bytes; %" PRIu64 " bytes all other atoms (%2.3lf%% atom overhead).\n", mdatData, file_size - mdatData, (double)(file_size - mdatData) / (double)file_size * 100.0); fprintf(stdout, "Total free atom space: %" PRIu32 " bytes; %2.3lf%% waste.", freeSpace, (double)freeSpace / (double)file_size * 100.0); if (freeSpace) { dynUpd.updage_by_padding = false; // APar_DetermineDynamicUpdate(true); //gets the size of the padding APar_Optimize( true); // just to know if 'free' atoms can be considered padding, or (in // the case of say a faac file) it's *just* 'free' if (!moov_atom_was_mooved) { fprintf(stdout, " Padding available: %" PRIu64 " bytes.", dynUpd.padding_bytes); } } if (gapless_void_padding > 0) { fprintf(stdout, "\nGapless playback null space at end of file: %" PRIu64 " bytes.", gapless_void_padding); } fprintf(stdout, "\n------------------------------------------------------\n"); ShowVersionInfo(); fprintf(stdout, "------------------------------------------------------\n"); free(tree_padding); tree_padding = NULL; return; } /*---------------------- APar_SimpleAtomPrintout print a simple flat list of atoms as they were created ----------------------*/ void APar_SimpleAtomPrintout() { // loop through each atom in the struct array // (which holds the offset info/data) printBOM(); for (int i = 0; i < atom_number; i++) { AtomicInfo *thisAtom = &parsedAtoms[i]; fprintf(stdout, "%i - Atom \"%s\" (level %u) has next atom at #%i\n", i, thisAtom->AtomicName, thisAtom->AtomicLevel, thisAtom->NextAtomNumber); } fprintf(stdout, "Total of %i atoms.\n", atom_number - 1); }