<commit_before>import gobject
gobject.threads_init()

import pygst
pygst.require('0.10')
import gst

from os.path import abspath
import datetime
import sys
import threading

from mopidy.utils.path import path_to_uri, find_files
from mopidy.models import Track, Artist, Album

def translator(data):
    album = Album(
        name=data['album'],
        num_tracks=data['track-count'],
    )

    artist = Artist(
        name=data['artist'],
    )

    date = datetime.date(
        data['date'].year,
        data['date'].month,
        data['date'].day,
    )

    return Track(
        uri=data['uri'],
        name=data['title'],
        album=album,
        artists=[artist],
        date=date,
        track_no=data['track-number'],
        length=data['duration'],
    )


class Scanner(object):
    def __init__(self, folder, data_callback, error_callback=None):
        self.uris = [path_to_uri(f) for f in find_files(folder)]
        self.data_callback = data_callback
        self.error_callback = error_callback
        self.loop = gobject.MainLoop()

        fakesink = gst.element_factory_make('fakesink')
        pad = fakesink.get_pad('sink')

        self.uribin = gst.element_factory_make('uridecodebin')
        self.uribin.connect('pad-added', self.process_new_pad, pad)

        self.pipe = gst.element_factory_make('pipeline')
        self.pipe.add(fakesink)
        self.pipe.add(self.uribin)

        bus = self.pipe.get_bus()
        bus.add_signal_watch()
        bus.connect('message::tag', self.process_tags)
        bus.connect('message::error', self.process_error)

    def process_new_pad(self, source, pad, target_pad):
        pad.link(target_pad)

    def process_tags(self, bus, message):
        data = message.parse_tag()
        data = dict([(k, data[k]) for k in data.keys()])
        data['uri'] = self.uribin.get_property('uri')
        data['duration'] = self.get_duration()
        self.data_callback(data)
        self.next_uri()

    def process_error(self, bus, message):
        if self.error_callback:
            uri = self.uribin.get_property('uri')
            errors = message.parse_error()
            self.error_callback(uri, errors)
        self.next_uri()

    def get_duration(self):
        self.pipe.get_state()
        try:
            return self.pipe.query_duration(
                gst.FORMAT_TIME, None)[0] // gst.MSECOND
        except gst.QueryError:
            return None

    def next_uri(self):
        if not self.uris:
            return self.stop()

        self.pipe.set_state(gst.STATE_NULL)
        self.uribin.set_property('uri', self.uris.pop())
        self.pipe.set_state(gst.STATE_PAUSED)

    def start(self):
        self.next_uri()
        self.loop.run()

    def stop(self):
        self.loop.quit()<commit_msg>Ensure that state is null on exit<commit_after>import gobject
gobject.threads_init()

import pygst
pygst.require('0.10')
import gst

from os.path import abspath
import datetime
import sys
import threading

from mopidy.utils.path import path_to_uri, find_files
from mopidy.models import Track, Artist, Album

def translator(data):
    album = Album(
        name=data['album'],
        num_tracks=data['track-count'],
    )

    artist = Artist(
        name=data['artist'],
    )

    date = datetime.date(
        data['date'].year,
        data['date'].month,
        data['date'].day,
    )

    return Track(
        uri=data['uri'],
        name=data['title'],
        album=album,
        artists=[artist],
        date=date,
        track_no=data['track-number'],
        length=data['duration'],
    )


class Scanner(object):
    def __init__(self, folder, data_callback, error_callback=None):
        self.uris = [path_to_uri(f) for f in find_files(folder)]
        self.data_callback = data_callback
        self.error_callback = error_callback
        self.loop = gobject.MainLoop()

        fakesink = gst.element_factory_make('fakesink')
        pad = fakesink.get_pad('sink')

        self.uribin = gst.element_factory_make('uridecodebin')
        self.uribin.connect('pad-added', self.process_new_pad, pad)

        self.pipe = gst.element_factory_make('pipeline')
        self.pipe.add(fakesink)
        self.pipe.add(self.uribin)

        bus = self.pipe.get_bus()
        bus.add_signal_watch()
        bus.connect('message::tag', self.process_tags)
        bus.connect('message::error', self.process_error)

    def process_new_pad(self, source, pad, target_pad):
        pad.link(target_pad)

    def process_tags(self, bus, message):
        data = message.parse_tag()
        data = dict([(k, data[k]) for k in data.keys()])
        data['uri'] = self.uribin.get_property('uri')
        data['duration'] = self.get_duration()
        self.data_callback(data)
        self.next_uri()

    def process_error(self, bus, message):
        if self.error_callback:
            uri = self.uribin.get_property('uri')
            errors = message.parse_error()
            self.error_callback(uri, errors)
        self.next_uri()

    def get_duration(self):
        self.pipe.get_state()
        try:
            return self.pipe.query_duration(
                gst.FORMAT_TIME, None)[0] // gst.MSECOND
        except gst.QueryError:
            return None

    def next_uri(self):
        if not self.uris:
            return self.stop()

        self.pipe.set_state(gst.STATE_NULL)
        self.uribin.set_property('uri', self.uris.pop())
        self.pipe.set_state(gst.STATE_PAUSED)

    def start(self):
        self.next_uri()
        self.loop.run()

    def stop(self):
        self.pipe.set_state(gst.STATE_NULL)
        self.loop.quit()
{'fraction_non_alphanumeric': 0.12455830388692579, 'fraction_numerical': 0.0014134275618374558, 'mean_word_length': 2.449725776965265, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5677472', 'n_tokens_mistral': 1839, 'n_tokens_neox': 1805, 'n_words': 326}