Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gstreamer-less flac encoder and tagging #121

Merged
merged 6 commits into from
Feb 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ install:

# Testing dependencies
- sudo apt-get install -qq gstreamer0.10-tools python-gst0.10
- sudo pip install twisted
- sudo pip install twisted mutagen

# Build bundled C utils
- cd src
Expand Down
2 changes: 1 addition & 1 deletion morituri/command/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def do(self):
logger.debug('Encoding %s to %s',
fromPath.encode('utf-8'),
toPath.encode('utf-8'))
encodetask = encode.EncodeTask(fromPath, toPath, profile)
encodetask = encode.FlacEncodeTask(fromPath, toPath)

runner.run(encodetask)

Expand Down
23 changes: 21 additions & 2 deletions morituri/common/checksum.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import os
import struct
import zlib
import binascii
import wave

import gst

Expand Down Expand Up @@ -262,8 +264,7 @@ def _eos_cb(self, sink):
logger.debug('eos, scheduling stop')
self.schedule(0, self.stop)


class CRC32Task(ChecksumTask):
class CRC32TaskOld(ChecksumTask):
"""
I do a simple CRC32 check.
"""
Expand All @@ -273,6 +274,24 @@ class CRC32Task(ChecksumTask):
def do_checksum_buffer(self, buf, checksum):
return zlib.crc32(buf, checksum)

class CRC32Task(etask.Task):
# TODO: Support sampleStart, sampleLength later on (should be trivial, just
# add change the read part in _crc32 to skip some samples and/or not
# read too far)
def __init__(self, path, sampleStart=0, sampleLength=-1):
self.path = path

def start(self, runner):
etask.Task.start(self, runner)
self.schedule(0.0, self._crc32)

def _crc32(self):
w = wave.open(self.path)
d = w._data_chunk.read()

self.checksum = binascii.crc32(d) & 0xffffffff
self.stop()


class FastAccurateRipChecksumTask(etask.Task):
description = 'Calculating (Fast) AccurateRip checksum'
Expand Down
45 changes: 45 additions & 0 deletions morituri/common/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@
import shutil
import tempfile

from mutagen.flac import FLAC

from morituri.common import common
from morituri.common import gstreamer as cgstreamer
from morituri.common import task as ctask

from morituri.extern.task import task, gstreamer
from morituri.program import sox
from morituri.program import flac

import logging
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -182,6 +185,48 @@ def _sox_peak(self):
self.peak = sox.peak_level(self.track_path)
self.stop()

class FlacEncodeTask(task.Task):
description = 'Encoding to FLAC'

def __init__(self, track_path, track_out_path, what="track"):
self.track_path = track_path
self.track_out_path = track_out_path
self.new_path = None
self.description = 'Encoding %s to FLAC' % what

def start(self, runner):
task.Task.start(self, runner)
self.schedule(0.0, self._flac_encode)

def _flac_encode(self):
self.new_path = flac.encode(self.track_path, self.track_out_path)
self.stop()

# TODO: Wizzup: Do we really want this as 'Task'...?
# I only made it a task for now because that it's easier to integrate in
# program/cdparanoia.py - where morituri currently does the tagging.
# We should just move the tagging to a more sensible place.
class TaggingTask(task.Task):
description = 'Writing tags to FLAC'

def __init__(self, track_path, tags):
self.track_path = track_path
self.tags = tags

def start(self, runner):
task.Task.start(self, runner)
self.schedule(0.0, self._tag)

def _tag(self):
w = FLAC(self.track_path)

for k, v in self.tags.items():
w[k] = v

w.save()

self.stop()

class EncodeTask(ctask.GstPipelineTask):
"""
I am a task that encodes a .wav file.
Expand Down
63 changes: 17 additions & 46 deletions morituri/common/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,58 +450,29 @@ def getTagList(self, number):
# htoa defaults to disc's artist
title = 'Hidden Track One Audio'

# here to avoid import gst eating our options
import gst

ret = gst.TagList()
tags = {}

# gst-python 0.10.15.1 does not handle unicode -> utf8 string
# conversion
# see http://bugzilla.gnome.org/show_bug.cgi?id=584445
if self.metadata and not self.metadata.various:
ret["album-artist"] = albumArtist.encode('utf-8')
ret[gst.TAG_ARTIST] = trackArtist.encode('utf-8')
ret[gst.TAG_TITLE] = title.encode('utf-8')
ret[gst.TAG_ALBUM] = disc.encode('utf-8')

# gst-python 0.10.15.1 does not handle tags that are UINT
# see gst-python commit 26fa6dd184a8d6d103eaddf5f12bd7e5144413fb
# FIXME: no way to compare against 'master' version after 0.10.15
if gst.pygst_version >= (0, 10, 15):
ret[gst.TAG_TRACK_NUMBER] = number
tags['ALBUMARTIST'] = albumArtist
tags['ARTIST'] = trackArtist
tags['TITLE'] = title
tags['ALBUM'] = disc

tags['TRACKNUMBER'] = u'%s' % number

if self.metadata:
# works, but not sure we want this
# if gst.pygst_version >= (0, 10, 15):
# ret[gst.TAG_TRACK_COUNT] = len(self.metadata.tracks)
# hack to get a GstDate which we cannot instantiate directly in
# 0.10.15.1
# FIXME: The dates are strings and must have the format 'YYYY',
# 'YYYY-MM' or 'YYYY-MM-DD'.
# GstDate expects a full date, so default to
# Jan and 1st if MM and DD are missing
date = self.metadata.release
if date:
logger.debug('Converting release date %r to structure', date)
if len(date) == 4:
date += '-01'
if len(date) == 7:
date += '-01'

s = gst.structure_from_string('hi,date=(GstDate)%s' %
str(date))
ret[gst.TAG_DATE] = s['date']

# no musicbrainz info for htoa tracks
tags['DATE'] = self.metadata.release

if number > 0:
ret["musicbrainz-trackid"] = mbidTrack
ret["musicbrainz-artistid"] = mbidTrackArtist
ret["musicbrainz-albumid"] = mbidAlbum
ret["musicbrainz-albumartistid"] = mbidTrackAlbum
ret["musicbrainz-discid"] = mbDiscId
tags['MUSICBRAINZ_TRACKID'] = mbidTrack
tags['MUSICBRAINZ_ARTISTID'] = mbidTrackArtist
tags['MUSICBRAINZ_ALBUMID'] = mbidAlbum
tags['MUSICBRAINZ_ALBUMARTISTID'] = mbidTrackAlbum
tags['MUSICBRAINZ_DISCID'] = mbDiscId

# FIXME: gst.TAG_ISRC
# TODO/FIXME: ISRC tag

return ret
return tags

def getHTOA(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions morituri/image/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ def add(index):
root, ext = os.path.splitext(os.path.basename(path))
outpath = os.path.join(outdir, root + '.' + profile.extension)
logger.debug('schedule encode to %r', outpath)
taskk = encode.EncodeTask(path, os.path.join(outdir,
root + '.' + profile.extension), profile)
taskk = encode.EncodeTaskFlac(path, os.path.join(outdir,
root + '.' + profile.extension))
self.addTask(taskk)

try:
Expand Down
12 changes: 9 additions & 3 deletions morituri/program/cdparanoia.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,18 @@ def __init__(self, path, table, start, stop, overread, offset=0,
# here to avoid import gst eating our options
from morituri.common import encode

self.tasks.append(encode.EncodeTask(tmppath, tmpoutpath, profile,
taglist=taglist, what=what))
self.tasks.append(encode.FlacEncodeTask(tmppath, tmpoutpath))

# MerlijnWajer: XXX: We run the CRC32Task on the wav file, because it's
# in general stupid to run the CRC32 on the flac file since it already
# has --verify. We should just get rid of this CRC32 step.
# make sure our encoding is accurate
self.tasks.append(checksum.CRC32Task(tmpoutpath))
self.tasks.append(checksum.CRC32Task(tmppath))
self.tasks.append(encode.SoxPeakTask(tmppath))

# TODO: Move tagging outside of cdparanoia
self.tasks.append(encode.TaggingTask(tmpoutpath, taglist))

self.checksum = None

def stop(self):
Expand Down
18 changes: 18 additions & 0 deletions morituri/program/flac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from subprocess import check_call, CalledProcessError

import logging
logger = logging.getLogger(__name__)

def encode(infile, outfile):
"""
Encodes infile to outfile, with flac.
Uses '-f' because morituri already creates the file.
"""
try:
# TODO: Replace with Popen so that we can catch stderr and write it to
# logging
check_call(['flac', '--silent', '--verify', '-o', outfile,
'-f', infile])
except CalledProcessError:
logger.exception('flac failed')
raise