Change InterpolatedFLAC to operate from memory; first working version of write()

This commit is contained in:
Martin Eve
2010-07-23 20:04:30 +01:00
parent 5baa443428
commit 02c04ffaf1
+125 -21
View File
@@ -26,6 +26,7 @@ import logging
from string import Template from string import Template
import operator import operator
import os import os
import tempfile
import re import re
import fuse import fuse
import errno import errno
@@ -35,8 +36,10 @@ import time
import calendar import calendar
import mutagen import mutagen
from mutagen import flac, id3 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 from mutagen.id3 import ID3
import io
from io import BytesIO
path_format = "$artist/$album ($year) [$format_upper]/$track - $artist - $title.$format" path_format = "$artist/$album ($year) [$format_upper]/$track - $artist - $title.$format"
@@ -268,12 +271,59 @@ class InterpolatedID3 (ID3):
f.close() f.close()
class InterpolatedFLAC (FLAC): 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): def get_header(self, filename=None):
if filename == None: f = self.fileobj
filename = self.filename f.seek(0)
f = open(filename, 'rb+')
# Ensure we've got padding at the end, and only at the end. # 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. # If adding makes it too large, we'll scale it down later.
@@ -302,9 +352,6 @@ class InterpolatedFLAC (FLAC):
if len(data) != available: if len(data) != available:
# We couldn't reduce the padding enough. # We couldn't reduce the padding enough.
diff = (len(data) - available) diff = (len(data) - available)
#insert_bytes(f, diff, header)
f.close()
self.__offset = len("fLaC" + data) self.__offset = len("fLaC" + data)
@@ -403,23 +450,31 @@ class FileHandler(object):
# now get the bounds of the file_class # now get the bounds of the file_class
#TODO: this needs to handle other file formats; use mutagen's detection procedure #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) #logging.info(self.real_path)
if format == "flac": if self.format == "flac":
self.inf = InterpolatedFLAC(self.real_path) #self.inf = InterpolatedFLAC(self.real_path)
self.inf = InterpolatedFLAC(self.file_object.read())
# get values from database # get values from database
self.inf["title"] = self.item.title self.inf["title"] = self.item.title
self.inf["album"] = self.item.album self.inf["album"] = self.item.album
self.inf["artist"] = self.item.artist self.inf["artist"] = self.item.artist
self.inf["genre"] = self.item.genre self.inf["genre"] = self.item.genre
self.inf["track"] = self.item.track
self.header = self.inf.get_header(self.real_path) self.header = self.inf.get_header(self.real_path)
self.bound = len(self.header) self.bound = len(self.header)
self.music_offset = self.inf.offset() self.music_offset = self.inf.offset()
elif format == "mp3":
elif self.format == "mp3":
self.bound = 0 # disable interpolation for now 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): def open(self):
# as init() handles actual opening, just increment instance count here # as init() handles actual opening, just increment instance count here
@@ -431,8 +486,6 @@ class FileHandler(object):
self.instance_count = self.instance_count - 1 self.instance_count = self.instance_count - 1
return False return False
else: else:
# if instance count is zero, close the physical file on disk
self.file_object.close()
return True return True
def read(self, size, offset): def read(self, size, offset):
@@ -441,22 +494,61 @@ class FileHandler(object):
if offset + size < len(self.header): if offset + size < len(self.header):
# can just read from header # can just read from header
logging.info("JUST HEADER")
ret = self.header[offset:offset+size] ret = self.header[offset:offset+size]
return ret return ret
else: else:
# get the header + some data from file # get the header + some data from file
logging.info("HEADER + DATA")
ret = self.header[offset:len(self.header)] ret = self.header[offset:len(self.header)]
self.file_object.seek(self.music_offset) ret = ret + self.music_data[0:size - ((len(self.header) - offset))]
ret = ret + self.file_object.read(size-(len(self.header) - offset))
return ret return ret
# otherwise, pass read call to underlying file system # otherwise, pass read call to underlying file system
self.file_object.seek(offset) #self.file_object.seek(offset)
return self.file_object.read(size) logging.info("JUST MUSIC")
return self.music_data[offset - len(self.header):offset - len(self.header) + size]
def write(self, offset, buf): def write(self, offset, buf):
# determine if offset is within header; if not, discard write # determine if offset is within header; if not, discard write
if offset < self.bound: 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 pass
class Stat(fuse.Stat): class Stat(fuse.Stat):
@@ -983,9 +1075,21 @@ class beetFileSystem(fuse.Fuse):
int, which is an errno code. int, which is an errno code.
""" """
logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh)) 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): def ftruncate(self, path, size, fh=None):
""" """