diff --git a/python_apps/airtime_analyzer/airtime_analyzer/analyzer_pipeline.py b/python_apps/airtime_analyzer/airtime_analyzer/analyzer_pipeline.py index e1393defb..bf467c190 100644 --- a/python_apps/airtime_analyzer/airtime_analyzer/analyzer_pipeline.py +++ b/python_apps/airtime_analyzer/airtime_analyzer/analyzer_pipeline.py @@ -7,6 +7,7 @@ from metadata_analyzer import MetadataAnalyzer from filemover_analyzer import FileMoverAnalyzer from cuepoint_analyzer import CuePointAnalyzer from replaygain_analyzer import ReplayGainAnalyzer +from playability_analyzer import * class AnalyzerPipeline: """ Analyzes and imports an audio file into the Airtime library. @@ -55,6 +56,7 @@ class AnalyzerPipeline: metadata = MetadataAnalyzer.analyze(audio_file_path, metadata) metadata = CuePointAnalyzer.analyze(audio_file_path, metadata) metadata = ReplayGainAnalyzer.analyze(audio_file_path, metadata) + metadata = PlayabilityAnalyzer.analyze(audio_file_path, metadata) metadata = FileMoverAnalyzer.move(audio_file_path, import_directory, original_filename, metadata) metadata["import_status"] = 0 # Successfully imported @@ -64,6 +66,11 @@ class AnalyzerPipeline: # Pass all the file metadata back to the main analyzer process, which then passes # it back to the Airtime web application. queue.put(metadata) + except UnplayableFileError as e: + logging.exception(e) + metadata["import_status"] = 2 + metadata["reason"] = "The file could not be played." + raise e except Exception as e: # Ensures the traceback for this child process gets written to our log files: logging.exception(e) diff --git a/python_apps/airtime_analyzer/airtime_analyzer/playability_analyzer.py b/python_apps/airtime_analyzer/airtime_analyzer/playability_analyzer.py new file mode 100644 index 000000000..0a3656296 --- /dev/null +++ b/python_apps/airtime_analyzer/airtime_analyzer/playability_analyzer.py @@ -0,0 +1,32 @@ +__author__ = 'asantoni' + +import subprocess +import logging +from analyzer import Analyzer + +class UnplayableFileError(Exception): + pass + +class PlayabilityAnalyzer(Analyzer): + ''' This class checks if a file can actually be played with Liquidsoap. ''' + + LIQUIDSOAP_EXECUTABLE = 'liquidsoap' + + @staticmethod + def analyze(filename, metadata): + ''' Checks if a file can be played by Liquidsoap. + :param filename: The full path to the file to analyzer + :param metadata: A metadata dictionary where the results will be put + :return: The metadata dictionary + ''' + command = [PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE, '-v', '-c', "output.dummy(audio_to_stereo(single('%s')))" % filename] + try: + subprocess.check_output(command, stderr=subprocess.STDOUT) + + except OSError as e: # liquidsoap was not found + logging.warn("Failed to run: %s - %s. %s" % (command[0], e.strerror, "Do you have liquidsoap installed?")) + except (subprocess.CalledProcessError, Exception) as e: # liquidsoap returned an error code + logging.warn(e) + raise UnplayableFileError + + return metadata diff --git a/python_apps/airtime_analyzer/tests/playability_analyzer_tests.py b/python_apps/airtime_analyzer/tests/playability_analyzer_tests.py new file mode 100644 index 000000000..3864d6b40 --- /dev/null +++ b/python_apps/airtime_analyzer/tests/playability_analyzer_tests.py @@ -0,0 +1,61 @@ +from nose.tools import * +from airtime_analyzer.playability_analyzer import * + +def check_default_metadata(metadata): + ''' Stub function for now in case we need it later.''' + pass + +def test_missing_liquidsoap(): + old_ls = PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE + PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE = 'foosdaf' + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo-utf8.mp3', dict()) + PlayabilityAnalyzer.LIQUIDSOAP_EXECUTABLE = old_ls # Need to put this back + +@raises(UnplayableFileError) +def test_invalid_filepath(): + metadata = PlayabilityAnalyzer.analyze(u'non-existent-file', dict()) + +def test_mp3_utf8(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo-utf8.mp3', dict()) + check_default_metadata(metadata) + +def test_mp3_dualmono(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-dualmono.mp3', dict()) + check_default_metadata(metadata) + +def test_mp3_jointstereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-jointstereo.mp3', dict()) + check_default_metadata(metadata) + +def test_mp3_simplestereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-simplestereo.mp3', dict()) + check_default_metadata(metadata) + +def test_mp3_stereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo.mp3', dict()) + check_default_metadata(metadata) + +def test_mp3_mono(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-mono.mp3', dict()) + check_default_metadata(metadata) + +def test_ogg_stereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo.ogg', dict()) + check_default_metadata(metadata) + +@raises(UnplayableFileError) +def test_invalid_wma(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo-invalid.wma', dict()) + +def test_m4a_stereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo.m4a', dict()) + check_default_metadata(metadata) + +def test_wav_stereo(): + metadata = PlayabilityAnalyzer.analyze(u'tests/test_data/44100Hz-16bit-stereo.wav', dict()) + check_default_metadata(metadata) + +@raises(UnplayableFileError) +def test_unknown(): + metadata = PlayabilityAnalyzer.analyze(u'http://www.google.com', dict()) + check_default_metadata(metadata) \ No newline at end of file