1# -*- coding: utf-8 -*- 2""" 3Convenience class for handling MSRecord and MSFileparam. 4""" 5from __future__ import (absolute_import, division, print_function, 6 unicode_literals) 7from future.builtins import * # NOQA 8 9import ctypes as C # NOQA 10import os 11 12import numpy as np 13 14from obspy import UTCDateTime 15from obspy.core.compatibility import from_buffer 16from .headers import HPTMODULUS, MS_NOERROR, MSFileParam, MSRecord, clibmseed 17 18 19def _get_ms_file_info(f, real_name): 20 """ 21 Takes a Mini-SEED filename as an argument and returns a dictionary 22 with some basic information about the file. Also suitable for Full 23 SEED. 24 25 This is an exact copy of a method of the same name in utils. Due to 26 circular imports this method cannot be import from utils. 27 XXX: Figure out a better way! 28 29 :param f: File pointer of opened file in binary format 30 :param real_name: Realname of the file, needed for calculating size 31 """ 32 # get size of file 33 info = {'filesize': os.path.getsize(real_name)} 34 pos = f.tell() 35 f.seek(0) 36 rec_buffer = from_buffer(f.read(512), dtype=np.int8) 37 info['record_length'] = clibmseed.ms_detect(rec_buffer, 512) 38 # Calculate Number of Records 39 info['number_of_records'] = int(info['filesize'] // 40 info['record_length']) 41 info['excess_bytes'] = info['filesize'] % info['record_length'] 42 f.seek(pos) 43 return info 44 45 46class _MSStruct(object): 47 """ 48 Class for handling MSRecord and MSFileparam. 49 50 It consists of a MSRecord and MSFileparam and an attached python file 51 pointer. 52 53 :ivar msr: MSRecord 54 :ivar msf: MSFileparam 55 :ivar file: filename 56 :ivar offset: Current offset 57 58 :param filename: file to attach to 59 :param init_msrmsf: initialize msr and msf structure 60 by a first pass of read. Setting this option to 61 false will result in errors when setting e.g. 62 the offset before a call to read 63 """ 64 def __init__(self, filename, init_msrmsf=True): 65 # Initialize MSRecord structure 66 self.msr = clibmseed.msr_init(C.POINTER(MSRecord)()) 67 self.msf = C.POINTER(MSFileParam)() # null pointer 68 self.file = filename 69 # dummy read once, to avoid null pointer in ms.msf for e.g. 70 # ms.offset 71 if init_msrmsf: 72 self.read(-1, 0, 1, 0) 73 self.offset = 0 74 75 def get_end(self): 76 """ 77 Return endtime 78 """ 79 self.read(-1, 0, 1, 0) 80 dtime = clibmseed.msr_endtime(self.msr) 81 return UTCDateTime(dtime / HPTMODULUS) 82 83 def get_start(self): 84 """ 85 Return starttime 86 """ 87 self.read(-1, 0, 1, 0) 88 dtime = clibmseed.msr_starttime(self.msr) 89 return UTCDateTime(dtime / HPTMODULUS) 90 91 def file_info(self): 92 """ 93 For details see util._get_ms_file_info 94 """ 95 fp = open(self.file, 'rb') 96 self.info = _get_ms_file_info(fp, self.file) 97 fp.close() 98 return self.info 99 100 def file_pos_from_rec_num(self, record_number=0): 101 """ 102 Return byte position of file given a certain record_number. 103 104 The byte position can be used to seek to certain points in the file 105 """ 106 if not hasattr(self, 'info'): 107 self.info = self.file_info() 108 # Calculate offset of the record to be read. 109 if record_number < 0: 110 record_number = self.info['number_of_records'] + record_number 111 if record_number < 0 or \ 112 record_number >= self.info['number_of_records']: 113 raise ValueError('Please enter a valid record_number') 114 return record_number * self.info['record_length'] 115 116 def read(self, reclen=-1, dataflag=1, skipnotdata=1, verbose=0, 117 raise_flag=True): 118 """ 119 Read MSRecord using the ms_readmsr_r function. The following 120 parameters are directly passed to ms_readmsr_r. 121 122 :param ms: _MSStruct (actually consists of a LP_MSRecord, 123 LP_MSFileParam and an attached file pointer). 124 Given an existing ms the function is much faster. 125 :param reclen: If reclen is 0 the length of the first record is auto- 126 detected. All subsequent records are then expected to have the 127 same record length. If reclen is negative the length of every 128 record is automatically detected. Defaults to -1. 129 :param dataflag: Controls whether data samples are unpacked, defaults 130 to 1. 131 :param skipnotdata: If true (not zero) any data chunks read that to do 132 not have valid data record indicators will be skipped. Defaults to 133 True (1). 134 :param verbose: Controls verbosity from 0 to 2. Defaults to None (0). 135 :param record_number: Number of the record to be read. The first record 136 has the number 0. Negative numbers will start counting from the end 137 of the file, e.g. -1 is the last complete record. 138 """ 139 errcode = clibmseed.ms_readmsr_r(C.pointer(self.msf), 140 C.pointer(self.msr), 141 self.file.encode('ascii', 'strict'), 142 reclen, None, None, 143 skipnotdata, dataflag, verbose) 144 if raise_flag: 145 if errcode != MS_NOERROR: 146 raise Exception("Error %d in ms_readmsr_r" % errcode) 147 return errcode 148 149 def __del__(self): 150 """ 151 Method for deallocating MSFileParam and MSRecord structure. 152 """ 153 errcode = clibmseed.ms_readmsr_r(C.pointer(self.msf), 154 C.pointer(self.msr), 155 None, -1, None, None, 0, 0, 0) 156 if errcode != MS_NOERROR: 157 raise Exception("Error %d in ms_readmsr_r" % errcode) 158 159 def set_offset(self, value): 160 self.msf.contents.readoffset = C.c_int(value) 161 162 def get_offset(self): 163 return int(self.msf.contents.readoffset) 164 165 offset = property(get_offset, set_offset) 166