Skip to content

Instantly share code, notes, and snippets.

@iluvcapra
Last active August 9, 2020 00:51
Show Gist options
  • Save iluvcapra/df42035c93005b80b0e0c3361d7add70 to your computer and use it in GitHub Desktop.
Save iluvcapra/df42035c93005b80b0e0c3361d7add70 to your computer and use it in GitHub Desktop.
extension Data {
func getInteger<Value: FixedWidthInteger>(at: Int, count: Int, as: Value.Type,
bigEndian : Bool = false) -> Value {
var rawValue = Value.zero
for i in 0..<count {
let byte = withUnsafeBytes { $0.load(fromByteOffset: at + i, as: UInt8.self) }
rawValue <<= 8
rawValue ^= Value(byte)
}
return bigEndian ? rawValue : Value(bigEndian: rawValue)
}
}
extension FileHandle {
func readInteger<Value: FixedWidthInteger>(count: Int, as: Value.Type,
bigEndian : Bool = false) -> Value {
let data = self.readData(ofLength: count)
return data.getInteger(at: 0, count: count, as: Value.self, bigEndian: bigEndian)
}
}
struct Bitfield<Value: FixedWidthInteger> {
let value : Value
func split(widths : [Int]) -> [Value] {
var ranges = [Range<Int>]()
var at = 0
for width in widths {
let thisRange = Range(uncheckedBounds: (lower: at, upper: at + width))
ranges.append(thisRange)
at += width
}
return ranges.map { self[$0] }
}
subscript(bounds : Range<Int>) -> Value {
let mask = Value( (1 << bounds.count) - 1 )
return (value >> bounds.startIndex) & mask
}
subscript(index : Int) -> Value {
return (value >> index) & 0x1
}
}
///
struct FlacFile {
let url : URL
struct StreamMarkerNotFoundError : Error { let position : UInt64 }
struct MetadataIterator: Sequence, IteratorProtocol {
private let file : FileHandle
private var state : Bool = true
struct Metadata {
let type: UInt8
let isLast: Bool
let data: Data
}
func expectStreamMarker() throws {
let offset = file.offsetInFile
guard let data = try file.read(upToCount: 4),
data == "fLaC".data(using: .ascii) else {
throw StreamMarkerNotFoundError(position: offset)
}
}
init(url : URL) throws {
self.file = try FileHandle(forReadingFrom: url)
try expectStreamMarker()
}
mutating func next() -> Metadata? {
guard state else { return nil }
let magic = file.readInteger(count: 1, as: UInt8.self)
let isLast = magic & 0x80 > 0
let blockType = magic & 0x7f
let dataSize = file.readInteger(count: 3, as: UInt32.self, bigEndian: true)
defer {
if isLast {
file.closeFile()
self.state = false
}
}
let data = file.readData(ofLength: Int(dataSize))
return Metadata(type: blockType, isLast: isLast, data: data)
}
}
func metadata() throws -> MetadataIterator {
return try MetadataIterator(url: url)
}
}
let f = FlacFile(url: Bundle.main.url(forResource: "file1", withExtension: "flac")!)
struct StreamInfoBlock {
let minimumBlockSize : Int
let maximumBlockSize : Int
let minimumFrameSize : Int
let maximumFrameSize : Int
let sampleRate : Int
let channelCount : Int
let bitsPerSample : Int
let totalSampleFrames : Int
let md5Signature : Data
static func from(data: Data) -> StreamInfoBlock {
let minBlockSize = data.getInteger(at: 0, count: 2, as: UInt16.self, bigEndian: true)
let maxBlockSize = data.getInteger(at: 2, count: 2, as: UInt16.self, bigEndian: true)
let minFrameSize = data.getInteger(at: 4, count: 3, as: UInt32.self, bigEndian: true)
let maxFrameSize = data.getInteger(at: 7, count: 3, as: UInt32.self, bigEndian: true)
let packed = data.getInteger(at: 10, count: 8, as: UInt64.self, bigEndian: true)
let bits = Bitfield(value: packed)
let sampleRate = bits[44..<64]
let channelCount = bits[41..<44] + 1
let bitsPerSample = bits[36..<41] + 1
let totalSampleFrames = bits[0..<36]
return StreamInfoBlock(minimumBlockSize: Int(minBlockSize),
maximumBlockSize: Int(maxBlockSize),
minimumFrameSize: Int(minFrameSize),
maximumFrameSize: Int(maxFrameSize),
sampleRate: Int(sampleRate),
channelCount: Int(channelCount),
bitsPerSample: Int(bitsPerSample),
totalSampleFrames: Int(totalSampleFrames),
md5Signature: Data(data[18..<34]))
}
}
struct VorbisCommentBlock {
let vendor : String
let comments : [String]
var collatedCommments : [String : [String]] {
var retval = [String : [String]]()
for comment in self.comments {
let (key, value) = VorbisCommentBlock.parse(comment: comment)
if retval.keys.contains(key) {
retval[key] = retval[key]! + [value]
} else {
retval[key] = [value]
}
}
return retval
}
static func parse(comment: String) -> (key: String, value: String) {
let p = Scanner(string: comment)
let k = p.scanUpToString("=") ?? ""
p.scanString("=")
let v = p.scanUpToString("\0") ?? ""
return (key: k.uppercased(), value: v)
}
static func from(data: Data) -> VorbisCommentBlock {
var offset = 0
let vendorLength = Int(data.getInteger(at: offset, count: 4, as: UInt32.self))
offset += 4
let vendorString = String(data: data[offset..<vendorLength+offset], encoding: .utf8)!
offset += vendorLength
let vectorCount = data.getInteger(at: offset, count: 4, as: UInt32.self)
offset += 4
var comments = [String]()
for _ in 0..<vectorCount {
let thisLength = Int(data.getInteger(at: offset, count: 4, as: UInt32.self))
offset += 4
let thisComment = String(data: data[offset..<offset+thisLength], encoding: .utf8)!
offset += thisLength
comments.append(thisComment)
}
return VorbisCommentBlock(vendor: vendorString, comments: comments)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment