Task 414: .MP3 File Format
Task 414: .MP3 File Format
MP3 File Format Specifications
The MP3 file format (MPEG-1/2 Audio Layer III) is a lossy audio compression standard defined in ISO/IEC 11172-3 (MPEG-1) and ISO/IEC 13818-3 (MPEG-2), with an unofficial MPEG-2.5 extension for lower sampling rates. MP3 files consist of a sequence of independent frames (each typically 26 ms long), optional ID3 metadata tags (v1 at the end or v2 at the beginning), and for variable bitrate (VBR) files, a special info frame like the Xing header. There is no overall file header; playback starts by syncing to the first frame. Frames include a 4-byte header followed by audio data (and optional 16-bit CRC). ID3 tags store metadata separately from the audio stream.
Key technical details:
- Frames: Each frame has a fixed number of samples (1152 for MPEG-1 Layer III, 576 for MPEG-2/2.5). Frame length varies by bitrate and sampling rate, calculated as
int((144 * bitrate * 1000 / sampling_rate) + padding)
. - Compression: Uses perceptual coding, MDCT, and psychoacoustics to discard inaudible data, achieving 75-95% size reduction compared to uncompressed audio.
- Bitrates: 8-320 kbps (depending on version/layer).
- Sampling Rates: 8-48 kHz (MPEG-1: 32/44.1/48 kHz; MPEG-2: 16/22.05/24 kHz; MPEG-2.5: 8/11.025/12 kHz).
- Channels: Mono, stereo, joint stereo, dual channel.
- VBR Support: Allowed, with optional Xing/Info header for seeking and duration info.
- ID3v1: Fixed 128-byte tag at file end.
- ID3v2: Variable-length tag at file start, with multiple frames for metadata.
- List of Properties Intrinsic to the File Format
These are the core structural and metadata properties extracted from the MP3 file structure, including frame headers, VBR info, and ID3 tags. They define the audio configuration and metadata without relying on external file system attributes (e.g., no OS timestamps or permissions).
Audio Frame Header Properties (per frame; typically consistent across file):
- MPEG Version (e.g., 1, 2, 2.5)
- Layer (e.g., III)
- Protection (CRC protected: yes/no)
- Bitrate (kbps, e.g., 128, 192, 320; or "free" for custom)
- Sampling Rate (Hz, e.g., 44100, 48000)
- Padding (yes/no; adds 1 byte/slot)
- Private Bit (application-specific: 0/1)
- Channel Mode (Stereo, Joint Stereo, Dual Channel, Mono)
- Mode Extension (for Joint Stereo: combinations of Intensity Stereo and MS Stereo on/off)
- Copyright (yes/no)
- Original (yes/no)
- Emphasis (None, 50/15 ms, CCIT J.17, Reserved)
VBR/Xing Header Properties (if present in first frame for VBR files):
- VBR Identifier ("Xing" or "Info")
- Flags (Frames, Bytes, TOC, VBR Scale)
- Number of Frames
- File Length (bytes)
- TOC (Table of Contents for seeking; 100 entries)
- VBR Scale (0-100; encoder-specific quality indicator)
ID3v1 Tag Properties (if present; fixed strings):
- Title (30 chars)
- Artist (30 chars)
- Album (30 chars)
- Year (4 chars)
- Comment (28 or 30 chars; v1.1 uses 28 for track number support)
- Track Number (if v1.1; byte value)
- Genre (byte index; e.g., 0=Blues, 12=Dance, 80=80's)
ID3v2 Tag Properties (if present; common frames):
- Title (TIT2)
- Artist (TPE1)
- Album (TALB)
- Year/Release Date (TYER or TDRC)
- Track Number (TRCK, e.g., "3/12")
- Genre (TCON, e.g., "(12)Dance" or free text)
- Composer (TCOM)
- Original Artist (TOPE)
- Copyright (TCOP)
- Encoded By (TENC)
- Comments (COMM; includes language and description)
- URL (WXXX or others like WOAR for artist URL)
- And potentially others (e.g., APIC for attached picture, but focused on text properties here)
Additional derived properties (calculated from headers/tags):
- Duration (seconds; from number of frames * samples per frame / sampling rate)
- Variable/Constant Bitrate (VBR/CBR)
- File Size (bytes)
- ID3 Version (v1, v2, or both)
- Two Direct Download Links for .MP3 Files
These are public domain audio files available for free download.
- Ghost Blog Embedded HTML/JavaScript for Drag-and-Drop MP3 Property Dump
This is a self-contained HTML snippet with JavaScript that can be embedded in a Ghost blog post. It creates a drop zone where users can drag an MP3 file. The script reads the file binary, parses key properties (focusing on frame header from first frame, ID3v1 if present, ID3v2 basics, and VBR if detected), and dumps them to the screen in a pre-formatted block. Note: This is browser-based (uses FileReader); writing/modifying files is not supported in browser JS for security reasons—use Node.js for that.
- Python Class for MP3 Property Handling
This Python class uses built-in modules (no external libs needed) to open an MP3 file, decode/read the properties, print them to console, and write (modify select properties like ID3v1 title and save a new file).
import struct
import os
class MP3Parser:
def __init__(self, filepath):
self.filepath = filepath
with open(filepath, 'rb') as f:
self.buffer = f.read()
self.props = self._parse()
def _parse(self):
props = {}
buffer = self.buffer
# ID3v2
if buffer[:3] == b'ID3':
major = buffer[3]
size = (buffer[6] << 21) | (buffer[7] << 14) | (buffer[8] << 7) | buffer[9]
offset = 10
props['id3v2'] = {}
while offset < size + 10:
frame_id = buffer[offset:offset+4].decode('ascii', errors='ignore')
if not frame_id.isalnum(): break
frame_size = struct.unpack('>I', buffer[offset+4:offset+8])[0]
frame_data = buffer[offset+10:offset+10+frame_size].decode('utf-8', errors='ignore').rstrip('\x00')
props['id3v2'][frame_id] = frame_data
offset += 10 + frame_size
# ID3v1
end = len(buffer)
if buffer[end-128:end-125] == b'TAG':
props['id3v1'] = {
'title': buffer[end-125:end-95].decode('ascii', errors='ignore').rstrip('\x00 '),
'artist': buffer[end-95:end-65].decode('ascii', errors='ignore').rstrip('\x00 '),
'album': buffer[end-65:end-35].decode('ascii', errors='ignore').rstrip('\x00 '),
'year': buffer[end-35:end-31].decode('ascii', errors='ignore').rstrip('\x00 '),
'comment': buffer[end-31:end-3].decode('ascii', errors='ignore').rstrip('\x00 '),
'track': buffer[end-2] if buffer[end-3] == 0 else None,
'genre': buffer[end-1]
}
# First frame header
header_offset = props.get('id3v2') and 10 + size or 0
while header_offset < end - 4:
if buffer[header_offset] == 0xFF and (buffer[header_offset + 1] & 0xE0) == 0xE0:
break
header_offset += 1
if header_offset < end - 4:
header = buffer[header_offset:header_offset+4]
version_bits = (header[1] >> 3) & 3
props['mpeg_version'] = 1 if version_bits == 3 else 2 if version_bits == 2 else 2.5
props['layer'] = 4 - ((header[1] >> 1) & 3)
props['protection'] = (header[1] & 1) == 0
bitrate_index = header[2] >> 4
bitrate_table = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0] # MPEG1 L3
props['bitrate'] = bitrate_table[bitrate_index]
sample_index = (header[2] >> 2) & 3
sample_table = [44100, 48000, 32000, 0]
props['sampling_rate'] = sample_table[sample_index]
props['padding'] = (header[2] >> 1) & 1
props['private_bit'] = header[2] & 1
channel_bits = header[3] >> 6
props['channel_mode'] = ['Stereo', 'Joint stereo', 'Dual channel', 'Mono'][channel_bits]
props['mode_extension'] = (header[3] >> 4) & 3
props['copyright'] = (header[3] >> 3) & 1
props['original'] = (header[3] >> 2) & 1
props['emphasis'] = header[3] & 3
# VBR Xing
xing_offset = header_offset + (36 if props['mpeg_version'] == 1 and props['channel_mode'] != 'Mono' else 21)
if buffer[xing_offset:xing_offset+4] in (b'Xing', b'Info'):
props['vbr'] = True
flags = struct.unpack('>I', buffer[xing_offset+4:xing_offset+8])[0]
props['vbr_flags'] = flags
props['num_frames'] = struct.unpack('>I', buffer[xing_offset+8:xing_offset+12])[0]
else:
props['vbr'] = False
props['file_size'] = len(buffer)
props['duration'] = props.get('num_frames') and (props['num_frames'] * 1152 / props['sampling_rate']) or 'Unknown (CBR)'
return props
def print_properties(self):
import json
print(json.dumps(self.props, indent=4))
def write(self, new_filepath, modifications={}):
buffer = bytearray(self.buffer)
# Example: Modify ID3v1 title if present
end = len(buffer)
if 'id3v1' in self.props and modifications.get('title'):
new_title = modifications['title'].ljust(30, ' ')[:30].encode('ascii')
buffer[end-125:end-95] = new_title
# Add more modifications as needed (e.g., ID3v2 is more complex)
with open(new_filepath, 'wb') as f:
f.write(buffer)
print(f"Modified file saved to {new_filepath}")
# Example usage:
# parser = MP3Parser('example.mp3')
# parser.print_properties()
# parser.write('modified.mp3', {'title': 'New Title'})
- Java Class for MP3 Property Handling
This Java class uses java.io for binary reading, parses properties, prints to console, and supports writing (modifying e.g., ID3v1 title and saving).
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;
public class MP3Parser {
private byte[] buffer;
private Map<String, Object> props;
public MP3Parser(String filepath) throws IOException {
buffer = Files.readAllBytes(Paths.get(filepath));
props = parse();
}
private Map<String, Object> parse() {
Map<String, Object> props = new HashMap<>();
// ID3v2
if (new String(buffer, 0, 3).equals("ID3")) {
int major = buffer[3] & 0xFF;
int size = ((buffer[6] & 0x7F) << 21) | ((buffer[7] & 0x7F) << 14) | ((buffer[8] & 0x7F) << 7) | (buffer[9] & 0x7F);
int offset = 10;
Map<String, String> id3v2 = new HashMap<>();
while (offset < size + 10) {
String frameId = new String(buffer, offset, 4);
if (!frameId.matches("^[A-Z0-9]{4}$")) break;
int frameSize = ByteBuffer.wrap(buffer, offset + 4, 4).getInt();
String frameData = new String(buffer, offset + 10, frameSize).trim().replaceAll("\0", "");
id3v2.put(frameId, frameData);
offset += 10 + frameSize;
}
props.put("id3v2", id3v2);
}
// ID3v1
int end = buffer.length;
if (new String(buffer, end - 128, 3).equals("TAG")) {
Map<String, Object> id3v1 = new HashMap<>();
id3v1.put("title", new String(buffer, end - 125, 30).trim());
id3v1.put("artist", new String(buffer, end - 95, 30).trim());
id3v1.put("album", new String(buffer, end - 65, 30).trim());
id3v1.put("year", new String(buffer, end - 35, 4).trim());
id3v1.put("comment", new String(buffer, end - 31, 28).trim());
id3v1.put("track", (buffer[end - 3] == 0) ? (buffer[end - 2] & 0xFF) : null);
id3v1.put("genre", buffer[end - 1] & 0xFF);
props.put("id3v1", id3v1);
}
// First frame header
int headerOffset = props.containsKey("id3v2") ? 10 + (int) ((Map) props.get("id3v2")).size() * 10 : 0; // Approximate
while (headerOffset < end - 4) {
if ((buffer[headerOffset] & 0xFF) == 0xFF && (buffer[headerOffset + 1] & 0xE0) == 0xE0) break;
headerOffset++;
}
if (headerOffset < end - 4) {
byte[] header = Arrays.copyOfRange(buffer, headerOffset, headerOffset + 4);
int versionBits = (header[1] >> 3) & 3;
props.put("mpegVersion", versionBits == 3 ? 1.0 : versionBits == 2 ? 2.0 : 2.5);
props.put("layer", 4 - ((header[1] >> 1) & 3));
props.put("protection", (header[1] & 1) == 0);
int bitrateIndex = header[2] >> 4;
int[] bitrateTable = {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0};
props.put("bitrate", bitrateTable[bitrateIndex]);
int sampleIndex = (header[2] >> 2) & 3;
int[] sampleTable = {44100, 48000, 32000, 0};
props.put("samplingRate", sampleTable[sampleIndex]);
props.put("padding", (header[2] >> 1) & 1);
props.put("privateBit", header[2] & 1);
int channelBits = header[3] >> 6;
String[] channels = {"Stereo", "Joint stereo", "Dual channel", "Mono"};
props.put("channelMode", channels[channelBits]);
props.put("modeExtension", (header[3] >> 4) & 3);
props.put("copyright", (header[3] >> 3) & 1);
props.put("original", (header[3] >> 2) & 1);
props.put("emphasis", header[3] & 3);
// VBR Xing
int xingOffset = headerOffset + ((Double) props.get("mpegVersion") == 1.0 && !props.get("channelMode").equals("Mono") ? 36 : 21);
String xing = new String(buffer, xingOffset, 4);
if (xing.equals("Xing") || xing.equals("Info")) {
props.put("vbr", true);
int flags = ByteBuffer.wrap(buffer, xingOffset + 4, 4).getInt();
props.put("vbrFlags", flags);
int numFrames = ByteBuffer.wrap(buffer, xingOffset + 8, 4).getInt();
props.put("numFrames", numFrames);
} else {
props.put("vbr", false);
}
}
props.put("fileSize", buffer.length);
if (props.containsKey("numFrames")) {
props.put("duration", ((Integer) props.get("numFrames")) * 1152.0 / ((Integer) props.get("samplingRate")));
} else {
props.put("duration", "Unknown (CBR)");
}
return props;
}
public void printProperties() {
System.out.println(props);
}
public void write(String newFilepath, Map<String, String> modifications) throws IOException {
byte[] newBuffer = buffer.clone();
int end = newBuffer.length;
if (props.containsKey("id3v1") && modifications.containsKey("title")) {
String newTitle = modifications.get("title");
byte[] titleBytes = newTitle.getBytes();
System.arraycopy(titleBytes, 0, newBuffer, end - 125, Math.min(30, titleBytes.length));
}
// Add more mods as needed
Files.write(Paths.get(newFilepath), newBuffer);
System.out.println("Modified file saved to " + newFilepath);
}
// Example usage:
// public static void main(String[] args) throws IOException {
// MP3Parser parser = new MP3Parser("example.mp3");
// parser.printProperties();
// Map<String, String> mods = new HashMap<>();
// mods.put("title", "New Title");
// parser.write("modified.mp3", mods);
// }
}
- JavaScript Class for MP3 Property Handling
This JavaScript class (Node.js compatible; requires fs module) opens an MP3 file, decodes/reads properties, prints to console, and writes (modifies e.g., ID3v1 title and saves).
const fs = require('fs');
class MP3Parser {
constructor(filepath) {
this.buffer = fs.readFileSync(filepath);
this.props = this.parse();
}
parse() {
const props = {};
const buffer = this.buffer;
// ID3v2
if (buffer.toString('ascii', 0, 3) === 'ID3') {
const major = buffer[3];
const size = (buffer[6] << 21) | (buffer[7] << 14) | (buffer[8] << 7) | buffer[9];
let offset = 10;
props.id3v2 = {};
while (offset < size + 10) {
const frameId = buffer.toString('ascii', offset, offset + 4);
if (!frameId.match(/^[A-Z0-9]{4}$/)) break;
const frameSize = buffer.readUInt32BE(offset + 4);
const frameData = buffer.toString('utf8', offset + 10, offset + 10 + frameSize).replace(/\0/g, '').trim();
props.id3v2[frameId] = frameData;
offset += 10 + frameSize;
}
}
// ID3v1
const end = buffer.length;
if (buffer.toString('ascii', end - 128, end - 125) === 'TAG') {
props.id3v1 = {
title: buffer.toString('ascii', end - 125, end - 95).trim(),
artist: buffer.toString('ascii', end - 95, end - 65).trim(),
album: buffer.toString('ascii', end - 65, end - 35).trim(),
year: buffer.toString('ascii', end - 35, end - 31).trim(),
comment: buffer.toString('ascii', end - 31, end - 3).trim(),
track: (buffer[end - 3] === 0) ? buffer[end - 2] : undefined,
genre: buffer[end - 1]
};
}
// First frame header
let headerOffset = props.id3v2 ? 10 + size : 0;
while (headerOffset < end - 4) {
if (buffer[headerOffset] === 0xFF && (buffer[headerOffset + 1] & 0xE0) === 0xE0) break;
headerOffset++;
}
if (headerOffset < end - 4) {
const header = buffer.slice(headerOffset, headerOffset + 4);
const versionBits = (header[1] >> 3) & 3;
props.mpegVersion = versionBits === 3 ? 1 : versionBits === 2 ? 2 : 2.5;
props.layer = 4 - ((header[1] >> 1) & 3);
props.protection = (header[1] & 1) === 0;
const bitrateIndex = header[2] >> 4;
const bitrateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0];
props.bitrate = bitrateTable[bitrateIndex];
const sampleIndex = (header[2] >> 2) & 3;
const sampleTable = [44100, 48000, 32000, 0];
props.samplingRate = sampleTable[sampleIndex];
props.padding = (header[2] >> 1) & 1;
props.privateBit = header[2] & 1;
const channelBits = header[3] >> 6;
props.channelMode = ['Stereo', 'Joint stereo', 'Dual channel', 'Mono'][channelBits];
props.modeExtension = (header[3] >> 4) & 3;
props.copyright = (header[3] >> 3) & 1;
props.original = (header[3] >> 2) & 1;
props.emphasis = header[3] & 3;
// VBR Xing
const xingOffset = headerOffset + (props.mpegVersion === 1 && props.channelMode !== 'Mono' ? 36 : 21);
const xing = buffer.toString('ascii', xingOffset, xingOffset + 4);
if (xing === 'Xing' || xing === 'Info') {
props.vbr = true;
props.vbrFlags = buffer.readUInt32BE(xingOffset + 4);
props.numFrames = buffer.readUInt32BE(xingOffset + 8);
} else {
props.vbr = false;
}
}
props.fileSize = buffer.length;
props.duration = props.numFrames ? (props.numFrames * 1152 / props.samplingRate) : 'Unknown (CBR)';
return props;
}
printProperties() {
console.log(JSON.stringify(this.props, null, 2));
}
write(newFilepath, modifications = {}) {
const newBuffer = Buffer.from(this.buffer);
const end = newBuffer.length;
if (this.props.id3v1 && modifications.title) {
const newTitle = modifications.title.padEnd(30, ' ').slice(0, 30);
newBuffer.write(newTitle, end - 125, 30, 'ascii');
}
// Add more mods as needed
fs.writeFileSync(newFilepath, newBuffer);
console.log(`Modified file saved to ${newFilepath}`);
}
}
// Example usage:
// const parser = new MP3Parser('example.mp3');
// parser.printProperties();
// parser.write('modified.mp3', { title: 'New Title' });
- C++ Class for MP3 Property Handling
This C++ class uses std::ifstream for reading, parses properties, prints to console, and supports writing (modifying e.g., ID3v1 title and saving). Compile with g++.
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <iomanip>
#include <cstring>
class MP3Parser {
private:
std::vector<char> buffer;
std::map<std::string, std::string> props; // Simplified to string for print
public:
MP3Parser(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
auto size = file.tellg();
buffer.resize(size);
file.seekg(0);
file.read(buffer.data(), size);
parse();
}
void parse() {
// ID3v2 (simplified)
if (std::strncmp(buffer.data(), "ID3", 3) == 0) {
int tag_size = ((buffer[6] & 0x7F) << 21) | ((buffer[7] & 0x7F) << 14) | ((buffer[8] & 0x7F) << 7) | (buffer[9] & 0x7F);
int offset = 10;
while (offset < tag_size + 10) {
char frame_id[5] = {0};
std::strncpy(frame_id, buffer.data() + offset, 4);
if (!std::isalnum(frame_id[0]) || !std::isalnum(frame_id[1]) || !std::isalnum(frame_id[2]) || !std::isalnum(frame_id[3])) break;
int frame_size = (buffer[offset + 4] << 24) | (buffer[offset + 5] << 16) | (buffer[offset + 6] << 8) | buffer[offset + 7];
std::string frame_data(buffer.begin() + offset + 10, buffer.begin() + offset + 10 + frame_size);
frame_data.erase(std::remove(frame_data.begin(), frame_data.end(), '\0'), frame_data.end());
props["id3v2_" + std::string(frame_id)] = frame_data;
offset += 10 + frame_size;
}
}
// ID3v1
size_t end = buffer.size();
if (std::strncmp(buffer.data() + end - 128, "TAG", 3) == 0) {
char temp[31] = {0};
std::strncpy(temp, buffer.data() + end - 125, 30); props["id3v1_title"] = std::string(temp);
std::strncpy(temp, buffer.data() + end - 95, 30); props["id3v1_artist"] = std::string(temp);
std::strncpy(temp, buffer.data() + end - 65, 30); props["id3v1_album"] = std::string(temp);
std::strncpy(temp, buffer.data() + end - 35, 4); props["id3v1_year"] = std::string(temp);
std::strncpy(temp, buffer.data() + end - 31, 28); props["id3v1_comment"] = std::string(temp);
if (buffer[end - 3] == 0) props["id3v1_track"] = std::to_string(static_cast<unsigned char>(buffer[end - 2]));
props["id3v1_genre"] = std::to_string(static_cast<unsigned char>(buffer[end - 1]));
}
// First frame header
size_t header_offset = props.count("id3v2_TIT2") > 0 ? 10 + 1024 : 0; // Approximate for ID3v2 size
while (header_offset < end - 4) {
if (static_cast<unsigned char>(buffer[header_offset]) == 0xFF && (static_cast<unsigned char>(buffer[header_offset + 1]) & 0xE0) == 0xE0) break;
header_offset++;
}
if (header_offset < end - 4) {
char header[4];
std::memcpy(header, buffer.data() + header_offset, 4);
int version_bits = (header[1] >> 3) & 3;
props["mpeg_version"] = std::to_string(version_bits == 3 ? 1 : version_bits == 2 ? 2 : 2.5);
props["layer"] = std::to_string(4 - ((header[1] >> 1) & 3));
props["protection"] = (header[1] & 1) == 0 ? "yes" : "no";
int bitrate_index = header[2] >> 4;
int bitrate_table[16] = {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0};
props["bitrate"] = std::to_string(bitrate_table[bitrate_index]);
int sample_index = (header[2] >> 2) & 3;
int sample_table[4] = {44100, 48000, 32000, 0};
props["sampling_rate"] = std::to_string(sample_table[sample_index]);
props["padding"] = std::to_string((header[2] >> 1) & 1);
props["private_bit"] = std::to_string(header[2] & 1);
int channel_bits = header[3] >> 6;
std::string channels[4] = {"Stereo", "Joint stereo", "Dual channel", "Mono"};
props["channel_mode"] = channels[channel_bits];
props["mode_extension"] = std::to_string((header[3] >> 4) & 3);
props["copyright"] = std::to_string((header[3] >> 3) & 1);
props["original"] = std::to_string((header[3] >> 2) & 1);
props["emphasis"] = std::to_string(header[3] & 3);
// VBR Xing
size_t xing_offset = header_offset + (std::stod(props["mpeg_version"]) == 1 && props["channel_mode"] != "Mono" ? 36 : 21);
char xing[5] = {0};
std::strncpy(xing, buffer.data() + xing_offset, 4);
if (std::strcmp(xing, "Xing") == 0 || std::strcmp(xing, "Info") == 0) {
props["vbr"] = "true";
int flags = (buffer[xing_offset + 4] << 24) | (buffer[xing_offset + 5] << 16) | (buffer[xing_offset + 6] << 8) | buffer[xing_offset + 7];
props["vbr_flags"] = std::to_string(flags);
int num_frames = (buffer[xing_offset + 8] << 24) | (buffer[xing_offset + 9] << 16) | (buffer[xing_offset + 10] << 8) | buffer[xing_offset + 11];
props["num_frames"] = std::to_string(num_frames);
} else {
props["vbr"] = "false";
}
}
props["file_size"] = std::to_string(buffer.size());
if (props.count("num_frames") > 0) {
double duration = std::stoi(props["num_frames"]) * 1152.0 / std::stoi(props["sampling_rate"]);
props["duration"] = std::to_string(duration);
} else {
props["duration"] = "Unknown (CBR)";
}
}
void printProperties() {
for (const auto& pair : props) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void write(const std::string& newFilepath, const std::map<std::string, std::string>& modifications) {
std::vector<char> newBuffer = buffer;
size_t end = newBuffer.size();
auto it = modifications.find("title");
if (props.count("id3v1_title") > 0 && it != modifications.end()) {
std::string newTitle = it->second;
newTitle.resize(30, ' ');
std::memcpy(newBuffer.data() + end - 125, newTitle.c_str(), 30);
}
// Add more mods as needed
std::ofstream out(newFilepath, std::ios::binary);
out.write(newBuffer.data(), newBuffer.size());
std::cout << "Modified file saved to " << newFilepath << std::endl;
}
};
// Example usage:
// int main() {
// MP3Parser parser("example.mp3");
// parser.printProperties();
// std::map<std::string, std::string> mods;
// mods["title"] = "New Title";
// parser.write("modified.mp3", mods);
// return 0;
// }