lofty 0.25.4

Audio metadata library
Documentation
use super::constants::ID3V1_TAG_MARKER;
use super::tag::Id3v1TagRef;
use crate::config::WriteOptions;
use crate::error::{FileEncodingError, TagEncodingError, TagParseError};
use crate::id3::v1::error::Id3v1EncodingError;
use crate::id3::{ID3FindResults, find_id3v1};
use crate::io::{Length, Truncate, VerifiedFile};
use crate::util::io::FileLike;
use crate::util::text::latin1_encode;

use std::io::{Cursor, Seek, Write};

use byteorder::WriteBytesExt;

#[allow(clippy::shadow_unrelated, clippy::collapsible_if)]
pub(crate) fn write_id3v1<F>(
	file: VerifiedFile<'_, F>,
	tag: &Id3v1TagRef<'_>,
	write_options: WriteOptions,
) -> Result<(), FileEncodingError>
where
	F: FileLike,
{
	let mut file = file.into_inner();

	// This will seek us to the writing position
	if let Some(ID3FindResults {
		header: _,
		content: _,
		range: _,
	}) = find_id3v1(&mut file, false, write_options.parse_options.parsing_mode)
		.map_err(TagParseError::from)?
	{
		if tag.is_empty() {
			// An ID3v1 tag occupies the last 128 bytes of the file, so we can just
			// shrink it down.
			let new_length = file.len()?.saturating_sub(128);
			file.truncate(new_length)?;

			return Ok(());
		}
	}

	let tag = encode(tag, write_options).map_err(TagEncodingError::from)?;
	file.write_all(&tag)?;

	Ok(())
}

pub(super) fn encode(
	tag: &Id3v1TagRef<'_>,
	write_options: WriteOptions,
) -> Result<Vec<u8>, Id3v1EncodingError> {
	fn resize_string(
		value: Option<&str>,
		size: usize,
		write_options: WriteOptions,
	) -> Result<Vec<u8>, Id3v1EncodingError> {
		let mut cursor = Cursor::new(vec![0; size]);
		cursor.rewind()?;

		if let Some(val) = value {
			for b in latin1_encode(val, write_options.lossy_text_encoding).take(size) {
				cursor.write_u8(b?)?;
			}
		}

		Ok(cursor.into_inner())
	}

	let mut writer = Vec::with_capacity(128);

	writer.write_all(&ID3V1_TAG_MARKER)?;

	let title = resize_string(tag.title, 30, write_options)?;
	writer.write_all(&title)?;

	let artist = resize_string(tag.artist, 30, write_options)?;
	writer.write_all(&artist)?;

	let album = resize_string(tag.album, 30, write_options)?;
	writer.write_all(&album)?;

	let mut year = [0; 4];
	if let Some(year_num) = tag.year {
		let mut year_num = std::cmp::min(year_num, 9999);

		let mut idx = 3;
		loop {
			year[idx] = b'0' + (year_num % 10) as u8;
			year_num /= 10;

			if idx == 0 {
				break;
			}

			idx -= 1;
		}
	}

	writer.write_all(&year)?;

	let comment = resize_string(tag.comment, 28, write_options)?;
	writer.write_all(&comment)?;

	writer.write_u8(0)?;

	writer.write_u8(tag.track_number.unwrap_or(0))?;
	writer.write_u8(tag.genre.unwrap_or(255))?;

	Ok(writer)
}