IDENTIFICATION DIVISION. PROGRAM-ID. MP3ID3INFO. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT MP3-FILE ASSIGN TO DYNAMIC WS-FILENAME ORGANIZATION IS BINARY SEQUENTIAL. DATA DIVISION. FILE SECTION. FD MP3-FILE. 01 FILE-RECORD PIC X(1). WORKING-STORAGE SECTION. *> Debug flag to enable/disable debug prints 01 WS-DEBUG-MODE PIC 9 VALUE 1. *> Default filename that can be overridden with command-line argument 01 WS-FILENAME PIC X(255) VALUE "audio.mp3". 01 WS-EOF-FLAG PIC 9 VALUE 0. 88 WS-EOF VALUE 1. *> Add a stop flag for zero-length frames 01 STOP-READING-FLAG PIC 9 VALUE 0. 88 STOP-READING VALUE 1. *> Command line argument handling 01 CMD-ARGS. 05 CMD-ARG-COUNT PIC 9(2) COMP-5. 05 CMD-ARG-VALUES PIC X(255) OCCURS 1 TO 10 DEPENDING ON CMD-ARG-COUNT. *> ID3v2 Header Structure 01 ID3-HEADER. 05 ID3-TAG PIC X(3). *> Should be "ID3" 05 ID3-VERSION PIC X(2). *> Version (ID3v2.X) 05 ID3-FLAGS PIC X(1). *> Flags 05 ID3-SIZE PIC X(4). *> Sync-safe size *> Frame Structure 01 ID3-FRAME-HEADER. 05 ID3-FRAME-ID PIC X(4). *> Frame identifier 05 ID3-FRAME-SIZE PIC X(4). *> Sync-safe frame size 05 ID3-FRAME-FLAGS PIC X(2). *> Frame flags *> Working variables - Use simpler COMP-5 instead of COMP for better debugger support 01 WS-TAG-SIZE PIC 9(9) COMP-5. 01 WS-FRAME-SIZE PIC 9(9) COMP-5. 01 WS-CURRENT-POS PIC 9(9) COMP-5. 01 WS-REMAINING-BYTES PIC S9(9) COMP-5. 01 WS-FILE-POS PIC 9(9) COMP-5 VALUE 1. 01 WS-I PIC 9(9) COMP-5. *> Buffer for frame data - Keep fixed size for easier debugging 01 WS-FRAME-DATA-SIZE PIC 9(5) COMP-5 VALUE 100000. 01 WS-FRAME-DATA. 05 WS-FRAME-CHAR PIC X OCCURS 100000 TIMES. *> Variables for sync-safe integer conversion 01 WS-BYTE PIC X OCCURS 4. 01 WS-BYTE-VAL PIC 9(3) COMP-5 OCCURS 4. *> Variables for sync-safe integer conversion 01 SYNC-SAFE-INT-IN. 05 SYNC-SAFE-INT-1 PIC X(1). 05 SYNC-SAFE-INT-2 PIC X(1). 05 SYNC-SAFE-INT-3 PIC X(1). 05 SYNC-SAFE-INT-4 PIC X(1). 01 SYNC-SAFE-INT-OUT PIC 9(9) COMP-5. *> Variables for zero-based ORD function 01 WS-TEMP-CHAR PIC X(1). 01 WS-ORD-RESULT PIC 9(5) COMP-5. 01 WS-BYTES-TO-READ PIC 9(5) COMP-5. 01 BYTES-BUFFER PIC X(100000). 01 WS-TEMP-BYTE PIC X(1). 01 WS-VAL PIC 9(7). 01 WS-NEW-VAL PIC 9(7). LINKAGE SECTION. 01 LS-CHAR PIC X(1). 01 LS-ORD-RESULT PIC 9(5) COMP-5. PROCEDURE DIVISION. MAIN-PARA. DISPLAY "Starting MP3-ID3-INFO program". *> Process command-line arguments PERFORM PROCESS-COMMAND-LINE DISPLAY "Analyzing file: " FUNCTION TRIM(WS-FILENAME) PERFORM OPEN-FILE. IF WS-EOF-FLAG = 0 PERFORM READ-ID3-HEADER IF ID3-TAG = "ID3" PERFORM PROCESS-ID3-TAG ELSE DISPLAY "No ID3v2 tag found!" END-IF END-IF. PERFORM CLOSE-FILE. DISPLAY "Program execution complete". STOP RUN. OPEN-FILE. DISPLAY "Opening file: " WS-FILENAME. OPEN INPUT MP3-FILE. IF WS-EOF-FLAG = 1 DISPLAY "Error opening file!" END-IF. CLOSE-FILE. DISPLAY "Closing file". CLOSE MP3-FILE. *> Read ID3 header READ-ID3-HEADER. *> Read ID3 header (10 bytes) MOVE 10 TO WS-BYTES-TO-READ PERFORM READ-BYTES MOVE BYTES-BUFFER(1:10) TO ID3-HEADER. PROCESS-ID3-TAG. *> Get the size which is 4-byte sync-safe integer MOVE ID3-SIZE TO SYNC-SAFE-INT-IN PERFORM CONVERT-SYNC-SAFE-INT MOVE SYNC-SAFE-INT-OUT TO WS-TAG-SIZE DISPLAY "ID3v2 Version: " FUNCTION TRIM(FUNCTION HEX-OF(ID3-VERSION(1:1))) "." FUNCTION TRIM(FUNCTION HEX-OF(ID3-VERSION(2:1))). *> Debug display IF WS-DEBUG-MODE = 1 DISPLAY "Tag Size: " WS-TAG-SIZE " bytes" END-IF MOVE 10 TO WS-FILE-POS. *> 10 bytes read so far (header) MOVE 0 TO STOP-READING-FLAG. *> Reset the stop reading flag *> Debug line with explicit break opportunity DISPLAY "Starting to read frames at position " WS-FILE-POS. PERFORM READ-FRAMES UNTIL (WS-FILE-POS >= WS-TAG-SIZE + 10) OR WS-EOF OR STOP-READING. READ-FRAMES. *> Reset values MOVE LOW-VALUES TO ID3-FRAME-HEADER. MOVE 0 TO WS-FRAME-SIZE. *> Read frame header (10 bytes) MOVE 10 TO WS-BYTES-TO-READ PERFORM READ-BYTES MOVE BYTES-BUFFER(1:10) TO ID3-FRAME-HEADER *> Check for all null bytes - this is definitely padding IF ID3-FRAME-ID = LOW-VALUES DISPLAY "****************************************" DISPLAY "Found padding (all nulls) - stopping frame reading" DISPLAY "****************************************" MOVE 1 TO STOP-READING-FLAG EXIT PARAGRAPH END-IF. *> Update file position ADD 10 TO WS-FILE-POS. DISPLAY "Current position: " WS-FILE-POS. *> Get Frame length MOVE ID3-FRAME-SIZE TO SYNC-SAFE-INT-IN PERFORM CONVERT-SYNC-SAFE-INT MOVE SYNC-SAFE-INT-OUT TO WS-FRAME-SIZE IF WS-DEBUG-MODE = 1 DISPLAY "-------------------------------" DISPLAY "FRAME HEADER: ID=" ID3-FRAME-ID ", SIZE=" WS-FRAME-SIZE END-IF *> Check for invalid Frame Length IF WS-FRAME-SIZE < 0 DISPLAY "Invalid WS-FRAME-SIZE " WS-FRAME-SIZE EXIT PARAGRAPH END-IF. *> Check for zero Frame Length - set stop flag and exit IF WS-FRAME-SIZE = 0 DISPLAY "****************************************" DISPLAY "Frame length zero - stopping frame reading" DISPLAY "****************************************" MOVE 1 TO STOP-READING-FLAG EXIT PARAGRAPH END-IF. *> Check for buffer overrun IF WS-FRAME-SIZE > 100000 DISPLAY "Frame too large to process: " WS-FRAME-SIZE PERFORM SKIP-FRAME ELSE PERFORM READ-FRAME-DATA END-IF. DISPLAY "Current position: " WS-FILE-POS. *> Update file position ADD WS-FRAME-SIZE TO WS-FILE-POS. COMPUTE WS-REMAINING-BYTES = (WS-TAG-SIZE + 10) - WS-FILE-POS. DISPLAY "Next position: " WS-FILE-POS. DISPLAY "Remaining: " WS-REMAINING-BYTES. SKIP-FRAME. PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-FRAME-SIZE READ MP3-FILE AT END MOVE 1 TO WS-EOF-FLAG EXIT PERFORM END-READ END-PERFORM. *> Read the Frame data *> We ignore pictures READ-FRAME-DATA. DISPLAY "Reading frame data: " WS-FRAME-SIZE " bytes". MOVE LOW-VALUES TO WS-FRAME-DATA. PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-FRAME-SIZE *> Add bounds check to prevent index errors IF WS-I >= 1 AND WS-I <= 100000 READ MP3-FILE INTO WS-TEMP-BYTE AT END MOVE 1 TO WS-EOF-FLAG EXIT PERFORM END-READ MOVE WS-TEMP-BYTE TO WS-FRAME-CHAR(WS-I) ELSE DISPLAY "WARNING: Index " WS-I " out of bounds (1-100000)" EXIT PERFORM END-IF END-PERFORM. *> Display appropriate message based on frame ID IF ID3-FRAME-ID = "APIC" DISPLAY "Frame: " ID3-FRAME-ID " - Data: [Picture]" ELSE DISPLAY "Frame: " ID3-FRAME-ID " - Data: " FUNCTION TRIM(WS-FRAME-DATA) END-IF. *> Convert sync-safe integer to actual size *> Implementation of syncSafeToInt function using zero-based ORD *> Input: SYNC-SAFE-INT-IN (4 bytes) *> Output: SYNC-SAFE-INT-OUT (integer value) CONVERT-SYNC-SAFE-INT. MOVE 0 TO WS-VAL. *> Extract the numeric value of each byte PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4 MOVE SYNC-SAFE-INT-IN(WS-I:1) TO WS-TEMP-CHAR MOVE 0 TO WS-ORD-RESULT CALL "ZERO-BASED-ORD" USING WS-TEMP-CHAR, WS-ORD-RESULT MOVE WS-ORD-RESULT TO WS-BYTE-VAL(WS-I) END-PERFORM *> Debug display of input bytes IF WS-DEBUG-MODE = 1 DISPLAY "Byte 1: " WS-BYTE-VAL(1) " (hex: " FUNCTION HEX-OF(SYNC-SAFE-INT-IN(1:1)) ")" DISPLAY "Byte 2: " WS-BYTE-VAL(2) " (hex: " FUNCTION HEX-OF(SYNC-SAFE-INT-IN(2:1)) ")" DISPLAY "Byte 3: " WS-BYTE-VAL(3) " (hex: " FUNCTION HEX-OF(SYNC-SAFE-INT-IN(3:1)) ")" DISPLAY "Byte 4: " WS-BYTE-VAL(4) " (hex: " FUNCTION HEX-OF(SYNC-SAFE-INT-IN(4:1)) ")" END-IF PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 4 MULTIPLY WS-VAL BY 256 GIVING WS-VAL MOVE WS-BYTE-VAL(WS-I) TO WS-NEW-VAL ADD WS-NEW-VAL TO WS-VAL END-PERFORM MOVE WS-VAL TO SYNC-SAFE-INT-OUT DISPLAY "Converted value: " SYNC-SAFE-INT-OUT. *> Input: WS-BYTES-TO-READ = number of bytes to read *> Output: BYTES-BUFFER filled with data READ-BYTES SECTION. MOVE SPACES TO BYTES-BUFFER PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-BYTES-TO-READ READ MP3-FILE INTO WS-TEMP-BYTE AT END MOVE 1 TO WS-EOF-FLAG EXIT PERFORM END-READ MOVE WS-TEMP-BYTE TO BYTES-BUFFER(WS-I:1) END-PERFORM. *> Process command-line arguments to get the filename PROCESS-COMMAND-LINE. *> Get argument count and values ACCEPT CMD-ARG-COUNT FROM ARGUMENT-NUMBER *> Check if at least one argument was provided IF CMD-ARG-COUNT > 0 *> Get the first argument (the filename) ACCEPT CMD-ARG-VALUES(1) FROM ARGUMENT-VALUE *> If argument isn't empty, use it as filename IF CMD-ARG-VALUES(1) NOT = SPACES MOVE CMD-ARG-VALUES(1) TO WS-FILENAME END-IF END-IF.