mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
244 lines
6.9 KiB
Swift
244 lines
6.9 KiB
Swift
import Foundation
|
|
|
|
private enum ID3Tag: CaseIterable {
|
|
case v2
|
|
case v3
|
|
|
|
static var headerLength = 5
|
|
var header: Data {
|
|
switch self {
|
|
case .v2:
|
|
return Data([ 0x49, 0x44, 0x33, 0x02, 0x00 ])
|
|
case .v3:
|
|
return Data([ 0x49, 0x44, 0x33, 0x03, 0x00 ])
|
|
}
|
|
}
|
|
|
|
var artworkHeader: Data {
|
|
switch self {
|
|
case .v2:
|
|
return Data([ 0x50, 0x49, 0x43 ])
|
|
case .v3:
|
|
return Data([ 0x41, 0x50, 0x49, 0x43 ])
|
|
}
|
|
}
|
|
|
|
var frameSizeOffset: Int {
|
|
switch self {
|
|
case .v2:
|
|
return 2
|
|
case .v3:
|
|
return 4
|
|
}
|
|
}
|
|
|
|
var frameOffset: Int {
|
|
switch self {
|
|
case .v2:
|
|
return 6
|
|
case .v3:
|
|
return 10
|
|
}
|
|
}
|
|
|
|
func frameSize(_ value: Int32) -> Int {
|
|
switch self {
|
|
case .v2:
|
|
return Int(value & 0x00ffffff) + self.frameOffset
|
|
case .v3:
|
|
return Int(value) + self.frameOffset
|
|
}
|
|
}
|
|
}
|
|
|
|
private let sizeOffset = 6
|
|
private let tagOffset = 10
|
|
private let artOffset = 10
|
|
|
|
private let v2FrameOffset: UInt32 = 6
|
|
private let v3FrameOffset: UInt32 = 10
|
|
|
|
private let tagEnding = Data([ 0x00, 0x00, 0x00 ])
|
|
|
|
private enum ID3ArtworkFormat: CaseIterable {
|
|
case jpg
|
|
case png
|
|
|
|
var magic: Data {
|
|
switch self {
|
|
case .jpg:
|
|
return Data([ 0xff, 0xd8, 0xff ])
|
|
case .png:
|
|
return Data([ 0x89, 0x50, 0x4e, 0x47 ])
|
|
}
|
|
}
|
|
}
|
|
|
|
private class DataStream {
|
|
private let data: Data
|
|
private(set) var position = 0
|
|
var reachedEnd: Bool {
|
|
return self.position >= self.data.count
|
|
}
|
|
|
|
init(_ data: Data) {
|
|
self.data = data
|
|
}
|
|
|
|
func next() -> UInt8? {
|
|
guard !self.reachedEnd else {
|
|
return nil
|
|
}
|
|
let byte = self.data[self.position]
|
|
self.position += 1
|
|
return byte
|
|
}
|
|
|
|
func nextValue<T: BinaryInteger>() -> T? {
|
|
let count = MemoryLayout<T>.size
|
|
guard self.position + count <= self.data.count else {
|
|
return nil
|
|
}
|
|
let value = self.data.subdata(in: self.position ..< self.position + count).withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> T in
|
|
return pointer.baseAddress!.assumingMemoryBound(to: T.self).pointee
|
|
}
|
|
self.position += count
|
|
return value
|
|
}
|
|
|
|
func next(_ count: Int, offset: Int = 0, peek: Bool = false) -> Data? {
|
|
guard self.position + offset + count <= self.data.count else {
|
|
return nil
|
|
}
|
|
let subdata = self.data.subdata(in: self.position + offset ..< self.position + offset + count)
|
|
if !peek {
|
|
self.position += count + offset
|
|
}
|
|
return subdata
|
|
}
|
|
|
|
func upTo(_ marker: UInt8) -> Data? {
|
|
if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) {
|
|
let upTo = self.next(end - self.position)
|
|
self.skip()
|
|
return upTo
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func skip(_ count: Int = 1) {
|
|
self.position += count
|
|
}
|
|
|
|
func skipThrough(_ marker: UInt8) {
|
|
if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) {
|
|
self.position = end + 1
|
|
} else {
|
|
self.position = self.data.count
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ID3ArtworkResult {
|
|
case notFound
|
|
case moreDataNeeded(Int)
|
|
case artworkData(Data)
|
|
}
|
|
|
|
func readAlbumArtworkData(_ data: Data) -> ID3ArtworkResult {
|
|
guard data.count >= 4 else {
|
|
return .notFound
|
|
}
|
|
|
|
let stream = DataStream(data)
|
|
let versionHeader = stream.next(ID3Tag.headerLength)
|
|
|
|
var version: ID3Tag?
|
|
for tag in ID3Tag.allCases {
|
|
if versionHeader == tag.header {
|
|
version = tag
|
|
break
|
|
}
|
|
}
|
|
guard let id3Tag = version else {
|
|
return .notFound
|
|
}
|
|
|
|
stream.skip()
|
|
guard let value: UInt32 = stream.nextValue() else {
|
|
return .notFound
|
|
}
|
|
let size = CFSwapInt32HostToBig(value)
|
|
let b1 = (size & 0x7f000000) >> 3
|
|
let b2 = (size & 0x007f0000) >> 2
|
|
let b3 = (size & 0x00007f00) >> 1
|
|
let b4 = size & 0x0000007f
|
|
let tagSize = Int(b1 + b2 + b3 + b4)
|
|
|
|
while !stream.reachedEnd {
|
|
guard let frameHeader = stream.next(4, peek: true) else {
|
|
return .moreDataNeeded(tagSize)
|
|
}
|
|
|
|
stream.skip(id3Tag.frameSizeOffset)
|
|
guard let value: UInt32 = stream.nextValue() else {
|
|
return .moreDataNeeded(tagSize)
|
|
}
|
|
let val = CFSwapInt32HostToBig(value)
|
|
if val > Int32.max {
|
|
return .notFound
|
|
}
|
|
let frameSize = id3Tag.frameSize(Int32(val))
|
|
let bytesLeft = frameSize - id3Tag.frameSizeOffset - 4
|
|
|
|
if frameHeader == id3Tag.artworkHeader {
|
|
var image: (ID3ArtworkFormat, Int)?
|
|
outer: for i in 0 ..< frameSize - 4 {
|
|
if let head = stream.next(4, offset: i, peek: true) {
|
|
for format in ID3ArtworkFormat.allCases {
|
|
if head.prefix(format.magic.count) == format.magic {
|
|
image = (format, i)
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let (format, offset) = image {
|
|
stream.skip(offset)
|
|
|
|
switch format {
|
|
case .jpg:
|
|
var data = Data(capacity: frameSize + 1024)
|
|
var previousByte: UInt8 = 0xff
|
|
|
|
let limit = Int(Double(frameSize - offset) * 0.8)
|
|
for _ in 0 ..< frameSize - offset {
|
|
if let byte: UInt8 = stream.nextValue() {
|
|
data.append(byte)
|
|
if byte == 0xd9 && previousByte == 0xff && data.count > limit {
|
|
break
|
|
}
|
|
previousByte = byte
|
|
} else {
|
|
return .moreDataNeeded(tagSize)
|
|
}
|
|
}
|
|
return .artworkData(data)
|
|
case .png:
|
|
if let data = stream.next(frameSize - offset) {
|
|
return .artworkData(data)
|
|
} else {
|
|
return .moreDataNeeded(tagSize)
|
|
}
|
|
}
|
|
}
|
|
} else if frameHeader.prefix(3) == tagEnding {
|
|
return .notFound
|
|
}
|
|
stream.skip(bytesLeft)
|
|
}
|
|
return .notFound
|
|
}
|