Passed
Push — dev ( ed36f9...ede60a )
by Konstantinos
05:48 queued 58s
created

music_album_creation.audio_segmentation.album_segmentation   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Test Coverage

Coverage 65.33%

Importance

Changes 0
Metric Value
eloc 81
dl 0
loc 137
ccs 49
cts 75
cp 0.6533
rs 10
c 0
b 0
f 0
wmc 21

8 Methods

Rating   Name   Duplication   Size   Complexity  
A AudioSegmenter.segment_from_file() 0 14 2
A AudioSegmenter.segment() 0 22 5
A AudioSegmenter.target_directory() 0 4 1
A AudioSegmenter._trans() 0 2 1
A AudioSegmenter.__init__() 0 2 1
A AudioSegmenter._segment() 0 14 4
A AudioSegmenter.segment_from_list() 0 28 5
A AudioSegmenter.__std_parameters() 0 5 1
1 1
import logging
2 1
import os
3 1
import subprocess
4 1
import tempfile
5 1
import time
6
7 1
from music_album_creation.tracks_parsing import StringParser
8
9 1
from .data import SegmentationInformation
10
11 1
logger = logging.getLogger(__name__)
12
13
14 1
class AudioSegmenter(object):
15
16 1
    def __init__(self, target_directory=tempfile.gettempdir()):
17 1
        self._dir = target_directory
18
19 1
    @property
20
    def target_directory(self):
21
        """The directory path that will serve as the destination for storing created tracks"""
22 1
        return self._dir
23
24 1
    @target_directory.setter
25
    def target_directory(self, directory_path):
26 1
        self._dir = directory_path
27
28 1
    def _trans(self, track_info):
29 1
        return [os.path.join(self._dir, '{}.mp3'.format(track_info[0]))] + track_info[1:]
30
31 1
    def segment(self, album_file, data, supress_stdout=True, supress_stderr=True, sleep_seconds=0):
32
        """
33
34
        :param album_file:
35
        :param data:
36
        :param supress_stdout:
37
        :param supress_stderr:
38
        :param float sleep_seconds:
39
        :return:
40
        """
41 1
        exit_code = 0
42 1
        i = 0
43 1
        while exit_code == 0 and i < len(data) - 1:
44 1
            time.sleep(sleep_seconds)
45 1
            exit_code = self._segment(album_file, *self._trans(list(data[i])), supress_stdout=supress_stdout, supress_stderr=supress_stderr)
46 1
            i += 1
47 1
        if exit_code != 0:
48
            raise FfmpegCommandError("Command '{}' failed".format(' '.join(self._args)))
49 1
        exit_code = self._segment(album_file, *self._trans(list(data[-1])), supress_stdout=supress_stdout, supress_stderr=supress_stderr)
50 1
        if exit_code != 0:
51
            raise FfmpegCommandError("Command '{}' failed".format(' '.join(self._args)))
52 1
        return [os.path.join(self._dir, '{}.mp3'.format(list(x)[0])) for x in data]
53
54 1
    def segment_from_list(self, album_file, data, supress_stdout=True, supress_stderr=True, sleep_seconds=0):
55
        """
56
        Given an album audio file and data structure with tracks information, segments the audio file into audio tracks which get stored in the 'self.target_directory' folder.\n
57
        :param str album_file:
58
        :param list data: list of lists. Each inner list must have 2 elements: track name and starting timestamp in hh:mm:ss
59
        :param bool supress_stdout:
60
        :param bool supress_stderr:
61
        :param bool verbose:
62
        :param float sleep_seconds:
63
        :return: full paths to audio tracks
64
        """
65
        # if not re.search('0:00', data[0][1]):
66
        #     raise NotStartingFromZeroTimestampError("First track ({}) is supposed to have a 0:00 timestamp. Instead {} found".format(data[0][0], data[0][1]))
67
68
        exit_code = 0
69
        data = StringParser.convert_tracks_data(data, album_file, target_directory=self._dir)
70
        audio_file_paths = [x[0] for x in data]
71
        i = 0
72
        while exit_code == 0 and i < len(data) - 1:
73
            time.sleep(sleep_seconds)
74
            exit_code = self._segment(album_file, *data[i], supress_stdout=supress_stdout, supress_stderr=supress_stderr)
75
            i += 1
76
        if exit_code != 0:
77
            raise FfmpegCommandError("Command '{}' failed".format(' '.join(self._args)))
78
        exit_code = self._segment(album_file, *data[-1], supress_stdout=supress_stdout, supress_stderr=supress_stderr)
79
        if exit_code != 0:
80
            raise FfmpegCommandError("Command '{}' failed".format(' '.join(self._args)))
81
        return audio_file_paths
82
83 1
    def segment_from_file(self, album_file, tracks_file, hhmmss_type, supress_stdout=True, supress_stderr=True, sleep_seconds=0):
84
        """
85
        Assumes the hhmmss are starting timestamps. Given an album audio file and a file with track information, segments the audio file into audio tracks which get stored in the 'self.target_directory' folder.\n
86
        :param str album_file:
87
        :param str tracks_file:
88
        :param str hhmmss_type:
89
        :param bool supress_stdout:
90
        :param bool supress_stderr:
91
        :param float sleep_seconds:
92
        :return:
93
        """
94 1
        with open(tracks_file, 'r') as f:
95 1
            segmentation_info = SegmentationInformation.from_multiline(f.read().strip(), hhmmss_type)
96 1
        return self.segment(album_file, segmentation_info, supress_stdout=supress_stdout, supress_stderr=supress_stderr, sleep_seconds=sleep_seconds)
97
98 1
    def _segment(self, *args, **kwargs):
99 1
        album_file = args[0]
100 1
        track_file = args[1]
101 1
        supress_stdout = kwargs['supress_stdout']
102 1
        supress_stderr = kwargs['supress_stderr']
103
104 1
        start = args[2]
105 1
        end = None
106 1
        if 3 < len(args):
107 1
            end = args[3]
108 1
        args = ['ffmpeg', '-y', '-i', '-acodec', 'copy', '-ss']
109 1
        self._args = args[:3] + ['{}'.format(album_file)] + args[3:] + [start] + (lambda: ['-to', str(end)] if end else [])() + ['{}'.format(track_file)]
110 1
        logger.info("Segmenting: '{}'".format(' '.join(self._args)))
111 1
        return subprocess.check_call(self._args, **self.__std_parameters(supress_stdout, supress_stderr))
112
        # ro = subprocess.run(self._args, **self.__std_parameters(supress_stdout, supress_stderr))
113
        # return ro.returncode
114
115 1
    @classmethod
116
    def __std_parameters(cls, std_out_flag, std_error_flag):
117
        """If an input flag is True then the stream  (either 'out' or 'err') can be obtained ie obj = subprocess.run(..); str(obj.stdout, encoding='utf-8')).\n
118
        If an input flag is False then the stream will be normally outputted; ie at the terminal"""
119 1
        return {v: subprocess.PIPE for k, v in zip([std_out_flag, std_error_flag], ['stdout', 'stderr']) if k}
120
121
122
class FfmpegCommandError(Exception): pass
123
124
125 1
if __name__ == '__main__':
126
    import sys
127
128
    if len(sys.argv) < 3:
129
        print('Usage: python3 Album_segmentation.py AUDIO_FILE TRACKS_FILE')
130
        sys.exit(1)
131
    try:
132
        audio_segmenter = AudioSegmenter()
133
        audio_segmenter.segment_from_file(sys.argv[1], sys.argv[2], supress_stdout=True, supress_stderr=True, verbose=True, sleep_seconds=0.45)
134
    except Exception as e:
135
        print(e)
136
        sys.exit(1)
137