aboutsummaryrefslogtreecommitdiffstats
path: root/brutha/file.py
blob: 3656629c6f693e58c972c6aff2e36c1c54a482c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
from __future__ import absolute_import

import os
import re
from datetime import datetime
from time import localtime

import mutagen
from mutagen.flac import FLACNoHeaderError, FLACVorbisError

from .util import escape


class NotAllowed(Exception):
    pass


def mtime(path):
    """
    Get a comparable modification time for a file.
    None if it does not exist.
    """
    if os.path.exists(path):
        return int(datetime.fromtimestamp(os.path.getmtime(path)).strftime('%Y%m%d%H%M%s'))


class File(object):
    def __init__(self, path, destpath, name, options):
        self.path = path
        self.destpath = destpath
        self.name = name
        self.options = options

    def destname(self):
        raise NotImplementedError()

    def pre(self):
        raise NotImplementedError()

    def post(self):
        commands = []
        if not self.uptodate():
            self.touch(commands)
        return commands

    def touch(self, commands):
        mtime = os.path.getmtime(self.src())
        i = localtime(mtime)
        stamp = "%s%s%s%s%s.%s" % (
                str(i.tm_year).zfill(4),
                str(i.tm_mon).zfill(2), str(i.tm_mday).zfill(2),
                str(i.tm_hour).zfill(2), str(i.tm_min).zfill(2),
                str(i.tm_sec).zfill(2))
        commands.append('touch -t%s -c -m %s' % (stamp, escape(self.dest())))

    def src(self):
        return os.path.join(self.path, self.name)

    def dest(self):
        return os.path.join(self.destpath, self.destname())

    def uptodate(self):
        return mtime(self.src()) == mtime(self.dest())


class FlacFile(File):
    PATTERN = re.compile(r'\.flac$', flags=re.IGNORECASE)

    def destname(self):
        return self.PATTERN.sub('.ogg', self.name)

    def pre(self):
        commands = []
        if not self.uptodate():
            self.transcode(commands)
        return commands

    def resample(self):
        rate = bits = ''
        if self.options['maxrate'] or self.options['maxbits']:
            try:
                f = mutagen.File(self.src())
            except FLACNoHeaderError:
                raise NotAllowed("Could not read FLAC header")
            except FLACVorbisError:
                raise NotAllowed("FLAC Vorbis error")
            if self.options['maxrate'] and f.info.sample_rate > self.options['maxrate']:
                rate = ' rate -v -L %s dither' % self.options['maxrate']
            if self.options['maxbits'] and f.info.bits_per_sample > self.options['maxbits']:
                bits = ' -b %s' % self.options['maxbits']
        return (rate, bits)

    def transcode(self, commands):
        rate, bits = self.resample()
        commands.append(
            'sox -V1 %(src)s -C %(quality)s%(bits)s %(dest)s%(rate)s' %
            dict(
                src=escape(self.src()),
                quality=self.options['quality'],
                bits=bits,
                dest=escape(self.dest()),
                rate=rate
            )
        )


class LossyFile(File):
    def destname(self):
        return self.name

    def pre(self):
        commands = []
        if not self.uptodate():
            self.copy(commands)
        return commands

    def copy(self, commands):
        if not self.sample_ok():
            raise NotAllowed("Sample rate or bit depth too high")
        commands.append('cp %s %s' % (escape(self.src()), escape(self.dest())))

    def sample_ok(self):
        if not self.options['lossycheck']:
            return True

        f = mutagen.File(self.src())
        try:
            if self.options['maxrate'] and f.info.sample_rate > self.options['maxrate']:
                return False
        except AttributeError:
            pass

        try:
            if self.options['maxbits'] and f.info.bits_per_sample > self.options['maxbits']:
                return False
        except AttributeError:
            pass

        return True