Change InterpolatedFLAC to operate from memory; first working version of write()
This commit is contained in:
+125
-21
@@ -26,6 +26,7 @@ import logging
|
||||
from string import Template
|
||||
import operator
|
||||
import os
|
||||
import tempfile
|
||||
import re
|
||||
import fuse
|
||||
import errno
|
||||
@@ -35,8 +36,10 @@ import time
|
||||
import calendar
|
||||
import mutagen
|
||||
from mutagen import flac, id3
|
||||
from mutagen.flac import FLAC, Padding, MetadataBlock
|
||||
from mutagen.flac import FLAC, Padding, MetadataBlock, VCFLACDict, CueSheet, SeekTable, FLACNoHeaderError
|
||||
from mutagen.id3 import ID3
|
||||
import io
|
||||
from io import BytesIO
|
||||
|
||||
path_format = "$artist/$album ($year) [$format_upper]/$track - $artist - $title.$format"
|
||||
|
||||
@@ -268,12 +271,59 @@ class InterpolatedID3 (ID3):
|
||||
f.close()
|
||||
|
||||
class InterpolatedFLAC (FLAC):
|
||||
|
||||
def load(self, filedata):
|
||||
self.metadata_blocks = []
|
||||
self.tags = None
|
||||
self.cuesheet = None
|
||||
self.seektable = None
|
||||
#self.filename = filename
|
||||
self.filedata = filedata
|
||||
self.fileobj = BytesIO(filedata)
|
||||
self.__check_header(self.fileobj)
|
||||
|
||||
while self.__read_metadata_block(self.fileobj):
|
||||
pass
|
||||
if self.fileobj.read(2) not in ["\xff\xf8", "\xff\xf9"]:
|
||||
raise FLACNoHeaderError("End of metadata did not start audio")
|
||||
|
||||
try:
|
||||
self.metadata_blocks[0].length
|
||||
except (AttributeError, IndexError):
|
||||
raise FLACNoHeaderError("Stream info block not found")
|
||||
|
||||
logging.info("Loaded INF")
|
||||
|
||||
def __read_metadata_block(self, file):
|
||||
byte = ord(file.read(1))
|
||||
size = to_int_be(file.read(3))
|
||||
try:
|
||||
data = file.read(size)
|
||||
if len(data) != size:
|
||||
raise error(
|
||||
"file said %d bytes, read %d bytes" % (size, len(data)))
|
||||
block = self.METADATA_BLOCKS[byte & 0x7F](data)
|
||||
except (IndexError, TypeError):
|
||||
block = MetadataBlock(data)
|
||||
block.code = byte & 0x7F
|
||||
self.metadata_blocks.append(block)
|
||||
else:
|
||||
self.metadata_blocks.append(block)
|
||||
if block.code == VCFLACDict.code:
|
||||
if self.tags is None: self.tags = block
|
||||
else: raise FLACVorbisError("> 1 Vorbis comment block found")
|
||||
elif block.code == CueSheet.code:
|
||||
if self.cuesheet is None: self.cuesheet = block
|
||||
else: raise error("> 1 CueSheet block found")
|
||||
elif block.code == SeekTable.code:
|
||||
if self.seektable is None: self.seektable = block
|
||||
else: raise error("> 1 SeekTable block found")
|
||||
return (byte >> 7) ^ 1
|
||||
|
||||
def get_header(self, filename=None):
|
||||
|
||||
if filename == None:
|
||||
filename = self.filename
|
||||
|
||||
f = open(filename, 'rb+')
|
||||
f = self.fileobj
|
||||
f.seek(0)
|
||||
|
||||
# Ensure we've got padding at the end, and only at the end.
|
||||
# If adding makes it too large, we'll scale it down later.
|
||||
@@ -302,9 +352,6 @@ class InterpolatedFLAC (FLAC):
|
||||
if len(data) != available:
|
||||
# We couldn't reduce the padding enough.
|
||||
diff = (len(data) - available)
|
||||
#insert_bytes(f, diff, header)
|
||||
|
||||
f.close()
|
||||
|
||||
self.__offset = len("fLaC" + data)
|
||||
|
||||
@@ -403,23 +450,31 @@ class FileHandler(object):
|
||||
|
||||
# now get the bounds of the file_class
|
||||
#TODO: this needs to handle other file formats; use mutagen's detection procedure
|
||||
format = os.path.splitext(path)[1][1:].lower()
|
||||
self.format = os.path.splitext(path)[1][1:].lower()
|
||||
#logging.info(self.real_path)
|
||||
if format == "flac":
|
||||
self.inf = InterpolatedFLAC(self.real_path)
|
||||
if self.format == "flac":
|
||||
#self.inf = InterpolatedFLAC(self.real_path)
|
||||
self.inf = InterpolatedFLAC(self.file_object.read())
|
||||
|
||||
# get values from database
|
||||
self.inf["title"] = self.item.title
|
||||
self.inf["album"] = self.item.album
|
||||
self.inf["artist"] = self.item.artist
|
||||
self.inf["genre"] = self.item.genre
|
||||
self.inf["track"] = self.item.track
|
||||
|
||||
self.header = self.inf.get_header(self.real_path)
|
||||
self.bound = len(self.header)
|
||||
self.music_offset = self.inf.offset()
|
||||
elif format == "mp3":
|
||||
|
||||
elif self.format == "mp3":
|
||||
self.bound = 0 # disable interpolation for now
|
||||
self.music_offset = 0 # disable interpolation for now
|
||||
|
||||
# read in the contents of the file
|
||||
self.file_object.seek(self.music_offset)
|
||||
self.music_data = self.file_object.read()
|
||||
# close the file
|
||||
self.file_object.close()
|
||||
|
||||
def open(self):
|
||||
# as init() handles actual opening, just increment instance count here
|
||||
@@ -431,8 +486,6 @@ class FileHandler(object):
|
||||
self.instance_count = self.instance_count - 1
|
||||
return False
|
||||
else:
|
||||
# if instance count is zero, close the physical file on disk
|
||||
self.file_object.close()
|
||||
return True
|
||||
|
||||
def read(self, size, offset):
|
||||
@@ -441,22 +494,61 @@ class FileHandler(object):
|
||||
|
||||
if offset + size < len(self.header):
|
||||
# can just read from header
|
||||
logging.info("JUST HEADER")
|
||||
ret = self.header[offset:offset+size]
|
||||
return ret
|
||||
else:
|
||||
# get the header + some data from file
|
||||
logging.info("HEADER + DATA")
|
||||
ret = self.header[offset:len(self.header)]
|
||||
self.file_object.seek(self.music_offset)
|
||||
ret = ret + self.file_object.read(size-(len(self.header) - offset))
|
||||
ret = ret + self.music_data[0:size - ((len(self.header) - offset))]
|
||||
return ret
|
||||
|
||||
# otherwise, pass read call to underlying file system
|
||||
self.file_object.seek(offset)
|
||||
return self.file_object.read(size)
|
||||
#self.file_object.seek(offset)
|
||||
logging.info("JUST MUSIC")
|
||||
return self.music_data[offset - len(self.header):offset - len(self.header) + size]
|
||||
|
||||
def write(self, offset, buf):
|
||||
# determine if offset is within header; if not, discard write
|
||||
|
||||
if offset < self.bound:
|
||||
# load interpolated file into memory
|
||||
|
||||
filedata = self.header + self.music_data
|
||||
|
||||
# patch in the new data
|
||||
filedata2 = filedata[0:offset] + buf + filedata[offset + len(buf):]
|
||||
filedata = filedata2
|
||||
|
||||
# create a new normal mutagen object
|
||||
if self.format == "flac":
|
||||
try:
|
||||
# now obtain a new Interpolated FLAC from the temporary file
|
||||
self.inf = InterpolatedFLAC(filedata)
|
||||
|
||||
# instead of putting the values into the FLAC, extract the values
|
||||
self.item.title = str(self.inf["title"][0]).encode('utf-8')
|
||||
self.item.album = str(self.inf["album"][0]).encode('utf-8')
|
||||
self.item.artist = str(self.inf["artist"][0]).encode('utf-8')
|
||||
self.item.genre = str(self.inf["genre"][0]).encode('utf-8')
|
||||
|
||||
self.lib.store(self.item)
|
||||
self.lib.save()
|
||||
|
||||
# get values from database
|
||||
self.inf["title"] = self.item.title
|
||||
self.inf["album"] = self.item.album
|
||||
self.inf["artist"] = self.item.artist
|
||||
self.inf["genre"] = self.item.genre
|
||||
|
||||
self.header = self.inf.get_header(self.real_path)
|
||||
self.bound = len(self.header)
|
||||
self.music_offset = self.inf.offset()
|
||||
|
||||
return len(buf)
|
||||
except IOError:
|
||||
logging.error("Couldn't update tag.")
|
||||
pass
|
||||
|
||||
class Stat(fuse.Stat):
|
||||
@@ -983,9 +1075,21 @@ class beetFileSystem(fuse.Fuse):
|
||||
int, which is an errno code.
|
||||
"""
|
||||
logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh))
|
||||
logging.debug(" buf: %r" % buf)
|
||||
|
||||
return -errno.EOPNOTSUPP
|
||||
if fh == None:
|
||||
file_path = None
|
||||
|
||||
try:
|
||||
if self.files == None:
|
||||
self.files = {"x":"y"}
|
||||
self.files[path] = FileHandler(path, self.lib)
|
||||
except:
|
||||
return -errno.EPERM
|
||||
|
||||
try:
|
||||
return self.files[path].write(offset, buf)
|
||||
except Exception as ex:
|
||||
logging.info(ex)
|
||||
|
||||
def ftruncate(self, path, size, fh=None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user