Kasianov Nikolai Alekseevich
2 years ago
9 changed files with 821 additions and 114 deletions
@ -0,0 +1,571 @@
|
||||
/* |
||||
rip - rip embedded content |
||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
use crate::util::position::Position; |
||||
use crate::util::content_type::ContentType; |
||||
|
||||
const ID3V2_IDENTIFIER: [u8; 3] = [0x49, 0x44, 0x33]; |
||||
const ID3V2_HEADER_LENGTH: usize = 10; |
||||
const MP3_HEADER_LENGTH: usize = 4; |
||||
const MP3_HEADER_SYNC_WORD_MASK: u32 = 0xFFE00000; // 11111111111000000000000000000000
|
||||
const MP3_HEADER_VERSION_MASK: u32 = 0x180000; // 00000000000110000000000000000000
|
||||
const MP3_HEADER_LAYER_MASK: u32 = 0x60000; // 00000000000001100000000000000000
|
||||
const MP3_HEADER_BITRATE_MASK: u32 = 0xF000; // 00000000000000001111000000000000
|
||||
const MP3_HEADER_SAMPLING_RATE_MASK: u32 = 0xC00; // 00000000000000000000110000000000
|
||||
const MP3_HEADER_PADDING_MASK: u32 = 0x200; // 00000000000000000000001000000000
|
||||
|
||||
#[derive(Debug)] |
||||
enum AudioVersion { |
||||
MpegV1, |
||||
MpegV2, |
||||
MpegV25, |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
enum LayerIndex { |
||||
LayerI, |
||||
LayerII, |
||||
LayerIII, |
||||
} |
||||
|
||||
fn get_bitrate(header: u32, audio_version: &AudioVersion, layer: &LayerIndex) -> Option<u16> { |
||||
match header & MP3_HEADER_BITRATE_MASK { |
||||
// 0001
|
||||
0x1000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI | LayerIndex::LayerII | LayerIndex::LayerIII => return Some(32), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(32), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(8), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// 0010
|
||||
0x2000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(64), |
||||
LayerIndex::LayerII => return Some(48), |
||||
LayerIndex::LayerIII => return Some(40), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(48), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(16), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 0011
|
||||
0x3000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(96), |
||||
LayerIndex::LayerII => return Some(56), |
||||
LayerIndex::LayerIII => return Some(48), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(32), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(16), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 0100
|
||||
0x4000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(128), |
||||
LayerIndex::LayerII => return Some(64), |
||||
LayerIndex::LayerIII => return Some(56), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(64), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(32), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 0101
|
||||
0x5000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(160), |
||||
LayerIndex::LayerII => return Some(80), |
||||
LayerIndex::LayerIII => return Some(64), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(80), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(40), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 0110
|
||||
0x6000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(192), |
||||
LayerIndex::LayerII => return Some(96), |
||||
LayerIndex::LayerIII => return Some(80), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(96), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(48), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 0111
|
||||
0x7000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(224), |
||||
LayerIndex::LayerII => return Some(112), |
||||
LayerIndex::LayerIII => return Some(96), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(112), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(56), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1000
|
||||
0x8000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(256), |
||||
LayerIndex::LayerII => return Some(128), |
||||
LayerIndex::LayerIII => return Some(112), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(128), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(64), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1001
|
||||
0x9000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(288), |
||||
LayerIndex::LayerII => return Some(160), |
||||
LayerIndex::LayerIII => return Some(128), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(144), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(80), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1010
|
||||
0xA000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(320), |
||||
LayerIndex::LayerII => return Some(192), |
||||
LayerIndex::LayerIII => return Some(160), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(160), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(96), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1011
|
||||
0xB000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(352), |
||||
LayerIndex::LayerII => return Some(224), |
||||
LayerIndex::LayerIII => return Some(192), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(176), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(112), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1100
|
||||
0xC000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(384), |
||||
LayerIndex::LayerII => return Some(256), |
||||
LayerIndex::LayerIII => return Some(224), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(192), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(128), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1101
|
||||
0xD000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(416), |
||||
LayerIndex::LayerII => return Some(320), |
||||
LayerIndex::LayerIII => return Some(256), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(224), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(144), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 1110
|
||||
0xE000 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(448), |
||||
LayerIndex::LayerII => return Some(384), |
||||
LayerIndex::LayerIII => return Some(320), |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 | AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return Some(256), |
||||
LayerIndex::LayerII | LayerIndex::LayerIII => return Some(160), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
_ => { |
||||
// invalid bitrate index
|
||||
return None; |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn get_samples_per_frame(audio_version: &AudioVersion, layer: &LayerIndex) -> u16 { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return 384, |
||||
LayerIndex::LayerII => return 1152, |
||||
LayerIndex::LayerIII => return 1152, |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV2 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return 384, |
||||
LayerIndex::LayerII => return 1152, |
||||
LayerIndex::LayerIII => return 576, |
||||
} |
||||
} |
||||
|
||||
AudioVersion::MpegV25 => { |
||||
match layer { |
||||
LayerIndex::LayerI => return 384, |
||||
LayerIndex::LayerII => return 1152, |
||||
LayerIndex::LayerIII => return 576, |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn get_sampling_rate(header: u32, audio_version: &AudioVersion) -> Option<u16> { |
||||
match header & MP3_HEADER_SAMPLING_RATE_MASK { |
||||
// 00
|
||||
0x0 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => return Some(44100), |
||||
AudioVersion::MpegV2 => return Some(22050), |
||||
AudioVersion::MpegV25 => return Some(11025), |
||||
} |
||||
} |
||||
|
||||
// 01
|
||||
0x400 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => return Some(48000), |
||||
AudioVersion::MpegV2 => return Some(24000), |
||||
AudioVersion::MpegV25 => return Some(12000), |
||||
} |
||||
} |
||||
|
||||
// 10
|
||||
0x800 => { |
||||
match audio_version { |
||||
AudioVersion::MpegV1 => return Some(32000), |
||||
AudioVersion::MpegV2 => return Some(16000), |
||||
AudioVersion::MpegV25 => return Some(8000), |
||||
} |
||||
} |
||||
|
||||
// invalid value
|
||||
_ => return None
|
||||
} |
||||
} |
||||
|
||||
fn get_audio_version(header: u32) -> Option<AudioVersion> { |
||||
match header & MP3_HEADER_VERSION_MASK { |
||||
// 00
|
||||
0x0 => return Some(AudioVersion::MpegV25), |
||||
// 10
|
||||
0x100000 => return Some(AudioVersion::MpegV2), |
||||
// 11
|
||||
0x180000 => return Some(AudioVersion::MpegV1), |
||||
// invalid version id
|
||||
_ => return None, |
||||
} |
||||
} |
||||
|
||||
fn get_layer(header: u32) -> Option<LayerIndex> { |
||||
match header & MP3_HEADER_LAYER_MASK { |
||||
// 01
|
||||
0x20000 => return Some(LayerIndex::LayerIII), |
||||
// 10
|
||||
0x40000 => return Some(LayerIndex::LayerII), |
||||
// 11
|
||||
0x60000 => return Some(LayerIndex::LayerI), |
||||
// invalid layer index
|
||||
_ => return None, |
||||
} |
||||
} |
||||
|
||||
pub fn rip_mp3(data: &[u8], start_index: usize) -> Option<Position> { |
||||
if data.len() < ID3V2_HEADER_LENGTH + MP3_HEADER_LENGTH + 1 || |
||||
start_index + ID3V2_HEADER_LENGTH + MP3_HEADER_LENGTH + 1 >= data.len() { |
||||
return None; |
||||
} |
||||
|
||||
let mut position: Position = Position{ |
||||
start: usize::MAX, |
||||
end: usize::MAX, |
||||
content_type: ContentType::MP3, |
||||
}; |
||||
|
||||
let mut cursor_index: usize; |
||||
for i in start_index..data.len() { |
||||
if i < ID3V2_HEADER_LENGTH && position.start == usize::MAX { |
||||
if data[i..i + ID3V2_IDENTIFIER.len()] == ID3V2_IDENTIFIER { |
||||
// found ID3v2 tag (the beginning of the MP3 file)
|
||||
println!("id3 at {}", i); |
||||
|
||||
// get tag length
|
||||
let mut tag_length_bytes: [u8; 4] = [0; 4]; |
||||
for j in 0..4 { |
||||
tag_length_bytes[j] = data[i+ID3V2_IDENTIFIER.len()+5+j]; |
||||
} |
||||
// convert from syncsafe to a normal integer
|
||||
let mut tag_length: u32 = 0; |
||||
for j in 0..4 { |
||||
tag_length = tag_length << 7; |
||||
tag_length = tag_length | tag_length_bytes[j] as u32; |
||||
} |
||||
|
||||
let id3v2_end_index: usize = i + ID3V2_HEADER_LENGTH + tag_length as usize; |
||||
println!("id3v2 end index is {}; the data size is {}", id3v2_end_index, data.len()); |
||||
if id3v2_end_index + MP3_HEADER_LENGTH > data.len() - 1 { |
||||
println!("not enough data length"); |
||||
// strange: there's a valid ID3 tag but not enough data to store any music
|
||||
break; |
||||
} |
||||
position.start = i; |
||||
|
||||
cursor_index = id3v2_end_index; |
||||
loop { |
||||
if cursor_index >= data.len() - 1 { |
||||
break; |
||||
} |
||||
|
||||
// whole header
|
||||
let mut mp3_header_bytes: [u8 ; MP3_HEADER_LENGTH] = [0; MP3_HEADER_LENGTH]; |
||||
for j in 0..MP3_HEADER_LENGTH { |
||||
mp3_header_bytes[j] = data[cursor_index + j]; |
||||
} |
||||
let mp3_header: u32 = u32::from_be_bytes(mp3_header_bytes); |
||||
|
||||
// check for sync word
|
||||
if !mp3_header & MP3_HEADER_SYNC_WORD_MASK == MP3_HEADER_SYNC_WORD_MASK { |
||||
break; |
||||
} |
||||
|
||||
// that's really an MP3 !
|
||||
println!("mpeg header at {}", cursor_index); |
||||
cursor_index += MP3_HEADER_LENGTH; |
||||
|
||||
println!("{:#032b}", mp3_header); |
||||
|
||||
// retrieve audio version
|
||||
let audio_version: AudioVersion; |
||||
match get_audio_version(mp3_header) { |
||||
Some(version) => audio_version = version, |
||||
None => break, |
||||
} |
||||
|
||||
println!("audio version is {:?}", audio_version); |
||||
|
||||
// get layer
|
||||
let layer: LayerIndex; |
||||
match get_layer(mp3_header) { |
||||
Some(l) => layer = l, |
||||
None => break, |
||||
} |
||||
|
||||
println!("layer is {:?}", layer); |
||||
|
||||
|
||||
// decode that HUGE bitrate table
|
||||
let bitrate: u16; |
||||
match get_bitrate(mp3_header, &audio_version, &layer) { |
||||
Some(rate) => bitrate = rate, |
||||
None => break, |
||||
} |
||||
|
||||
println!("bitrate is {}", bitrate); |
||||
|
||||
|
||||
// samples per frame
|
||||
// let samples_per_frame: u16 = get_samples_per_frame(&audio_version, &layer);
|
||||
|
||||
// sampling rate
|
||||
let sampling_rate: u16; |
||||
match get_sampling_rate(mp3_header, &audio_version) { |
||||
Some(rate) => sampling_rate = rate, |
||||
None => break, |
||||
} |
||||
|
||||
println!("sampling rate is {}", sampling_rate); |
||||
|
||||
|
||||
// padding
|
||||
let padding: u8; |
||||
if mp3_header == MP3_HEADER_PADDING_MASK { |
||||
padding = 1; |
||||
} else { |
||||
padding = 0;
|
||||
} |
||||
|
||||
println!("padding is {}", padding); |
||||
|
||||
|
||||
// finally calculate frame size
|
||||
let frame_size: u32; |
||||
match layer { |
||||
LayerIndex::LayerI => { |
||||
frame_size = (12 * bitrate as u32 * 1000 / sampling_rate as u32 + padding as u32) * 4; |
||||
}
|
||||
|
||||
LayerIndex::LayerII | LayerIndex::LayerIII => { |
||||
frame_size = 144 * bitrate as u32 * 1000 / (sampling_rate as u32 + padding as u32); |
||||
} |
||||
} |
||||
println!("frame size is {}", frame_size); |
||||
|
||||
// set cursor to the next frame
|
||||
cursor_index += frame_size as usize; |
||||
// extend end position
|
||||
position.end = cursor_index; |
||||
println!("frame end at {}", cursor_index); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if position.start != usize::MAX && position.end != usize::MAX { |
||||
break; |
||||
} |
||||
}
|
||||
|
||||
if position.start == usize::MAX || position.end == usize::MAX || position.end <= position.start { |
||||
return None; |
||||
} |
||||
|
||||
return Some(position); |
||||
} |
@ -0,0 +1,64 @@
|
||||
/* |
||||
rip - rip embedded content |
||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
use crate::util::position::Position; |
||||
use crate::util::content_type::ContentType; |
||||
|
||||
const JPEG_IDENTIFIER: [u8; 3] = [0xFF, 0xD8, 0xFF]; |
||||
const JPEG_END_IDENTIFIER: [u8; 2] = [0xFF, 0xD9]; |
||||
|
||||
// Reads data from specified start_index position,
|
||||
// if valid png bytes were found - returns exact positions of an image
|
||||
pub fn rip_jpeg(data: &[u8], start_index: usize) -> Option<Position> { |
||||
if data.len() < JPEG_IDENTIFIER.len() + JPEG_END_IDENTIFIER.len() || |
||||
start_index + JPEG_IDENTIFIER.len() + JPEG_END_IDENTIFIER.len() > data.len() { |
||||
return None; |
||||
} |
||||
|
||||
let mut position: Position = Position{ |
||||
start: usize::MAX, |
||||
end: usize::MAX, |
||||
content_type: ContentType::JPEG, |
||||
}; |
||||
|
||||
for i in start_index..data.len() { |
||||
// start index
|
||||
if i < data.len() - JPEG_IDENTIFIER.len() && position.start == usize::MAX { |
||||
if data[i..i + JPEG_IDENTIFIER.len()] == JPEG_IDENTIFIER { |
||||
position.start = i; |
||||
} |
||||
} |
||||
|
||||
// end index
|
||||
if i <= data.len() - JPEG_END_IDENTIFIER.len() && position.end == usize::MAX { |
||||
if data[i..i + JPEG_END_IDENTIFIER.len()] == JPEG_END_IDENTIFIER { |
||||
position.end = i; |
||||
} |
||||
} |
||||
|
||||
if position.start != usize::MAX && position.end != usize::MAX { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if position.start == usize::MAX || position.end == usize::MAX || position.end < position.start { |
||||
return None; |
||||
} |
||||
|
||||
return Some(position); |
||||
} |
@ -0,0 +1,65 @@
|
||||
/* |
||||
rip - rip embedded content |
||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
use crate::util::position::Position; |
||||
use crate::util::content_type::ContentType; |
||||
|
||||
const PNG_IDENTIFIER: [u8; 8] = [0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA]; |
||||
const PNG_END_IDENTIFIER: [u8; 8] = [0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]; |
||||
|
||||
// Reads data from specified start_index position,
|
||||
// if valid png bytes were found - returns exact positions of an image
|
||||
pub fn rip_png(data: &[u8], start_index: usize) -> Option<Position> { |
||||
if data.len() < PNG_IDENTIFIER.len() + PNG_END_IDENTIFIER.len() || |
||||
start_index + PNG_IDENTIFIER.len() + PNG_END_IDENTIFIER.len() > data.len() { |
||||
return None; |
||||
} |
||||
|
||||
let mut position: Position = Position{ |
||||
start: usize::MAX, |
||||
end: usize::MAX, |
||||
content_type: ContentType::PNG, |
||||
}; |
||||
|
||||
for i in start_index..data.len() { |
||||
// start index
|
||||
if i < data.len() - PNG_IDENTIFIER.len() && position.start == usize::MAX { |
||||
if data[i..i + PNG_IDENTIFIER.len()] == PNG_IDENTIFIER { |
||||
position.start = i; |
||||
} |
||||
} |
||||
|
||||
// end index
|
||||
if i <= data.len() - PNG_END_IDENTIFIER.len() && position.end == usize::MAX { |
||||
if data[i..i + PNG_END_IDENTIFIER.len()] == PNG_END_IDENTIFIER { |
||||
position.end = i + PNG_END_IDENTIFIER.len(); |
||||
println!("end {}", position.end); |
||||
} |
||||
} |
||||
|
||||
if position.start != usize::MAX && position.end != usize::MAX { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if position.start == usize::MAX || position.end == usize::MAX || position.end <= position.start { |
||||
return None; |
||||
} |
||||
|
||||
return Some(position); |
||||
} |
@ -0,0 +1,24 @@
|
||||
/* |
||||
rip - rip embedded content |
||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#[derive(Debug)] |
||||
pub enum ContentType { |
||||
PNG, |
||||
JPEG, |
||||
MP3, |
||||
} |
@ -0,0 +1,26 @@
|
||||
/* |
||||
rip - rip embedded content |
||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
use crate::util::content_type::ContentType; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct Position { |
||||
pub start: usize, |
||||
pub end: usize, |
||||
pub content_type: ContentType, |
||||
} |
Loading…
Reference in new issue