Commits

iamsultan committed bf4b1c9

* Added
* tests
* setup.py
* README, CHANGES, LICENSE, AUTHORS

  • Participants
  • Parent commits 7077d3e

Comments (0)

Files changed (14)

+syntax: glob
+media/*
+*.idea
+*.DS_Store
+*.pyc
+*.idea
+*.db
+*.txt
+Sultan Imanhodjaev <sultan.imanhodjaev@gmail.com>

CHANGES

Empty file added.
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+Yamdi
+*****
+
+::
+
+    Yamdi is a Python wrapper around the command line tool yamdi (github.com/ioppermann/yamdi).
+    You’ll need yamdi installed in order for the package to be of any use.
+    If you are on OSX I recommend using homebrew (brew install yamdi).
+
+    The Yamdi command line tool expects to receive a path to an flv file and will return XML file of the extracted
+    metadata that we then wrap in a Python object and use to expose the metadata.
+
+    Current package version only supports the reading of metadata. The command line tool also supports the writing of
+    the metadata into a new .flv file but this is not yet supported in this package.
+
+    Tested under Python 2.6.1
+
+Requirements
+************
+::
+
+    pip install BeautifulSoup
+    brew install yamdi
+
+Usage
+*****
+
+::
+
+    >>> from yamdi import Yamdi
+    >>> flvmeta = Yamdi(flv_path="/path/to/flv")
+    >>> flvmeta.duration
+    10.4
+    >>> for keyframe in flvmeta.key_frames:
+    ...    keyframe.time # gets time value
+    ...    keyframe.file_position # gets position in file with respect to a time
+
+Supported methods
+*****************
+* duration - float, in seconds
+* has_keyframe - boolean
+* has_video - boolean
+* has_audio - boolean
+* has_metadata - boolean
+* has_cue_points - boolean
+* can_seek_to_end - boolean
+* audio_codec_id - integer
+* audio_sample_rate - integer
+* audio_data_rate - integer
+* audio_sample_size -integer
+* audio_delay - float, in seconds
+* stereo - boolean
+* video_codec_id - integer
+* frame_rate - float, in seconds
+* video_data_rate - integer
+* height - integer
+* width - integer
+* data_size - integer
+* audio_size - integer
+* video_size - integer
+* file_size - integer
+* last_timestamp - float, in seconds
+* last_video_frame_timestamp - float, in seconds
+* last_key_frame_timestamp - float, in seconds
+* last_key_frame_location - integer
+* key_frames - Array of key frame data, each item contains the following
+    * time - float, in seconds
+    * file_position - integer
+
+Have fun!

frames.py

-# -*- coding: utf-8 -*-
-class KeyFrame:
-    def __init__(self, time=None, file_position=None):
-        self.time = time
-        self.file_position = file_position
-
-    def all_from_xml(self, metadata):
-        self.metadata = metadata
-        times = self.get(tag="times", func=float)
-        file_positions = self.get(tag="filepositions", func=int)
-        keyframes = []
-
-        for cnt in range(len(times)):
-            keyframes.append(KeyFrame(time=times[cnt], file_position=file_positions[cnt]))
-
-        return keyframes
-
-    def get(self, tag, func):
-        results = []
-
-        for time in self.value(tag=tag):
-            for val in time.findAll("value"):
-                results.append(func("".join(val.contents)))
-
-        return results
-
-    def value(self, tag=None, first=False, soup=None):
-        if tag is None:
-            raise NameError("Please provide tag should not be None")
-        if soup:
-            target = soup.first(tag)
-        else:
-            target = self.metadata.first(tag)
-        return "".join(target.contents) if first else self.metadata(tag)
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from setuptools import setup, find_packages
+
+setup(
+    name="python-yamdi",
+    version = u":versiontools:yamdi:",
+    description="FLV metadata extractor",
+    long_description="Wrapper around Yamdi cli utility to extract metadata from flv files",
+    keywords="pyhton, yamdi, flv, flvtools",
+    author="Sultan Imanhodjaev",
+    author_email="sultan.imanhodjaev@gmail.com",
+    url="https://bitbucket.org/iamsultan/yamdi",
+    license="LGPL",
+    include_package_data=True,
+    packages=find_packages(),
+    install_requires=[
+        "distribute",
+        "BeautifulSoup"
+    ],
+    setup_requires=[
+        "versiontools >= 1.8",
+    ],
+    classifiers=[
+        "Development Status :: 1 - Alfa",
+        "Environment :: System",
+        "Intended Audience :: Developers",
+        "Intended Audience :: Information Technology",
+        "Intended Audience :: System Administrators",
+        "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+        "Natural Language :: English",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python",
+        "Topic :: Utilities",
+    ]
+)

tests/__init__.py

+__author__ = 'sultan'

tests/files/sample.flv

Binary file added.
+import os
+import unittest
+import sys
+
+current_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+sys.path.insert(0, current_path)
+
+class YamdiTestSequence(unittest.TestCase):
+    def setUp(self, *args, **kwargs):
+        from yamdi import yamdi
+        self.flv_path = os.path.join(os.path.dirname(__file__), "files", "sample.flv")
+        self.y = yamdi.Yamdi(flv_path=self.flv_path)
+
+    def test_duration(self):
+        self.assertEqual(self.y.duration, 6.06)
+
+    def test_has_keyframes(self):
+        self.assertTrue(self.y.has_keyframes)
+
+    def test_has_video(self):
+        self.assertTrue(self.y.has_video)
+
+    def test_has_audio(self):
+        self.assertTrue(self.y.has_audio)
+
+    def test_has_metadata(self):
+        self.assertTrue(self.y.has_metadata)
+
+    def test_has_cue_points(self):
+        self.assertFalse(self.y.has_cue_points)
+
+    def test_can_seek_to_end(self):
+        self.assertTrue(self.y.can_seek_to_end)
+
+    def test_audio_codec_id(self):
+        self.assertEqual(self.y.audio_codec_id, 2)
+
+    def test_audio_sample_rate(self):
+        self.assertEqual(self.y.audio_sample_rate, 3)
+
+    def test_audio_data_rate(self):
+        self.assertEqual(self.y.audio_data_rate, 94)
+
+    def test_audio_sample_size(self):
+        self.assertEqual(self.y.audio_sample_size, 1)
+
+    def test_audio_delay(self):
+        self.assertEqual(self.y.audio_delay, 0.00)
+
+    def test_stereo(self):
+        self.assertTrue(self.y.stereo)
+
+    def test_video_codec_id(self):
+        self.assertEqual(self.y.video_codec_id, 4)
+
+    def test_frame_rate(self):
+        self.assertEqual(self.y.frame_rate, 0.33)
+
+    def test_video_data_rate(self):
+        self.assertEqual(self.y.video_data_rate, 14)
+
+    def test_height(self):
+        self.assertEqual(self.y.height, 288)
+
+    def test_width(self):
+        self.assertEqual(self.y.width, 360)
+
+    def test_data_size(self):
+        self.assertEqual(self.y.data_size, 88470)
+
+    def test_audio_size(self):
+        self.assertEqual(self.y.audio_size, 75958)
+
+    def test_video_size(self):
+        self.assertEqual(self.y.video_size, 11572)
+
+    def test_file_size(self):
+        self.assertEqual(self.y.file_size, 89137)
+
+    def test_last_timestamp(self):
+        self.assertEqual(self.y.last_timestamp, 6.06)
+
+    def test_last_video_frame_timestamp(self):
+        self.assertEqual(self.y.last_video_frame_timestamp, 6.04)
+
+    def test_last_key_frame_timestamp(self):
+        self.assertEqual(self.y.last_video_frame_timestamp, 6.04)
+
+    def test_last_key_frame_location(self):
+        self.assertEqual(self.y.last_key_frame_location, 83017)
+
+if __name__ == "__main__":
+    unittest.main()

yamdi.py

-# -*- coding: utf-8 -*-
-import os, tempfile
-
-from BeautifulSoup import BeautifulStoneSoup
-from frames import KeyFrame
-
-class Yamdi:
-    def __init__(self, flv_path):
-        self.flv_path = flv_path
-        self.metadata = BeautifulStoneSoup(self.extract_meta(flv_path))
-
-    @property
-    def duration(self):
-        return float(self.value(tag="duration", first=True))
-
-    @property
-    def width(self):
-        return int(self.value(tag="width", first=True))
-
-    @property
-    def height(self):
-        return int(self.value(tag="height", first=True))
-
-    @property
-    def has_keyframes(self):
-        return self.boolean(self.value(tag="hasKeyframes", first=True))
-
-    @property
-    def has_video(self):
-        return self.boolean(self.value(tag="hasVideo", first=True))
-
-    @property
-    def has_audio(self):
-        return self.boolean(self.value(tag="hasAudio", first=True))
-
-    @property
-    def has_metadata(self):
-        return self.boolean(self.value(tag="hasMetadata", first=True))
-
-    @property
-    def has_cue_points(self):
-        return self.boolean(self.value(tag="hasCuePoint", first=True))
-
-    @property
-    def can_seek_to_end(self):
-        return self.boolean(self.value(tag="canSeekToEnd", first=True))
-
-    @property
-    def audio_codec_id(self):
-        return int(self.value(tag="audiocodecid", first=True))
-
-    @property
-    def audio_sample_rate(self):
-        return int(self.value(tag="audiosamplerate", first=True))
-
-    @property
-    def audio_data_rate(self):
-        return int(self.value(tag="audiodatarate", first=True))
-
-    @property
-    def audio_sample_size(self):
-        return int(self.value(tag="audiosamplesize", first=True))
-
-    @property
-    def audio_delay(self):
-        return float(self.value(tag="audiodelay", first=True))
-
-    @property
-    def stereo(self):
-        return self.boolean(text=self.value(tag="stereo", first=True))
-
-    @property
-    def video_codec_id(self):
-        return int(self.value(tag="videocodecid", first=True))
-
-    @property
-    def frame_rate(self):
-        return float(self.value(tag="framerate", first=True))
-
-    @property
-    def video_data_rate(self):
-        return int(self.value(tag="videodatarate", first=True))
-
-    @property
-    def data_size(self):
-        return int(self.value(tag="datasize", first=True))
-
-    @property
-    def audio_size(self):
-        return int(self.value(tag="audiosize", first=True))
-
-    @property
-    def video_size(self):
-        return int(self.value(tag="videosize", first=True))
-
-    @property
-    def file_size(self):
-        return int(self.value(tag="filesize", first=True))
-
-    @property
-    def last_timestamp(self):
-        return float(self.value(tag="last_timestamp", first=True))
-
-    @property
-    def last_video_frame_timestamp(self):
-        return float(self.value(tag="lastvideoframetimestamp", first=True))
-
-    @property
-    def last_key_frame_timestamp(self):
-        return float(self.value(tag="lastkeyframetimestamp", first=True))
-
-    @property
-    def last_key_frame_location(self):
-        return float(self.value(tag="lastkeyframelocation", first=True))
-
-    @property
-    def key_frames(self):
-        return KeyFrame().all_from_xml(self.metadata)
-
-    def value(self, tag=None, first=False):
-        if tag is None:
-            raise NameError("Please provide tag should not be None")
-        target = self.metadata.first(tag)
-        return "".join(target.contents) if first else self.metadata(tag)
-
-    def extract_meta(self, flv_path):   
-        temp_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
-        os.system("yamdi -i {flv_path} -x {temp_file}".format(flv_path=flv_path, temp_file=temp_file.name))
-        temp = open(temp_file.name, "rb")
-        contents = temp.readlines()
-        temp.close()
-        temp_file.close()
-        os.unlink(temp_file.name)
-        return ''.join(contents)
-
-    def boolean(self, text):
-        if "true" in text: return True
-        elif "false" in text: return False

yamdi/__init__.py

+__author__ = 'sultan'
+# -*- coding: utf-8 -*-
+class KeyFrame:
+    def __init__(self, time=None, file_position=None):
+        self.time = time
+        self.file_position = file_position
+
+    def all_from_xml(self, metadata):
+        self.metadata = metadata
+        times = self.get(tag="times", func=float)
+        file_positions = self.get(tag="filepositions", func=int)
+        keyframes = []
+
+        for cnt in range(len(times)):
+            keyframes.append(KeyFrame(time=times[cnt], file_position=file_positions[cnt]))
+
+        return keyframes
+
+    def get(self, tag, func):
+        results = []
+
+        for time in self.value(tag=tag):
+            for val in time.findAll("value"):
+                results.append(func("".join(val.contents)))
+
+        return results
+
+    def value(self, tag=None, first=False):
+        if tag is None:
+            raise NameError("Please provide tag should not be None")
+        target = self.metadata.first(tag)
+        if first and target:
+            return "".join(target.contents)
+        else:
+            return self.metadata(tag)
+# -*- coding: utf-8 -*-
+import os, tempfile
+
+from BeautifulSoup import BeautifulStoneSoup
+from frames import KeyFrame
+
+class Yamdi:
+    def __init__(self, flv_path):
+        self.flv_path = flv_path
+        self.metadata = BeautifulStoneSoup(self.extract_meta(flv_path))
+
+    @property
+    def duration(self):
+        return float(self.value(tag="duration"))
+
+    @property
+    def width(self):
+        return int(self.value(tag="width"))
+
+    @property
+    def height(self):
+        return int(self.value(tag="height"))
+
+    @property
+    def has_keyframes(self):
+        return self.boolean(self.value(tag="hasKeyframes"))
+
+    @property
+    def has_video(self):
+        return self.boolean(self.value(tag="hasVideo"))
+
+    @property
+    def has_audio(self):
+        return self.boolean(self.value(tag="hasAudio"))
+
+    @property
+    def has_metadata(self):
+        return self.boolean(self.value(tag="hasMetadata"))
+
+    @property
+    def has_cue_points(self):
+        return self.boolean(self.value(tag="hasCuePoint"))
+
+    @property
+    def can_seek_to_end(self):
+        return self.boolean(self.value(tag="canSeekToEnd"))
+
+    @property
+    def audio_codec_id(self):
+        return int(self.value(tag="audiocodecid"))
+
+    @property
+    def audio_sample_rate(self):
+        return int(self.value(tag="audiosamplerate"))
+
+    @property
+    def audio_data_rate(self):
+        return int(self.value(tag="audiodatarate"))
+
+    @property
+    def audio_sample_size(self):
+        return int(self.value(tag="audiosamplesize"))
+
+    @property
+    def audio_delay(self):
+        return float(self.value(tag="audiodelay"))
+
+    @property
+    def stereo(self):
+        return self.boolean(text=self.value(tag="stereo"))
+
+    @property
+    def video_codec_id(self):
+        return int(self.value(tag="videocodecid"))
+
+    @property
+    def frame_rate(self):
+        return float(self.value(tag="framerate"))
+
+    @property
+    def video_data_rate(self):
+        return int(self.value(tag="videodatarate"))
+
+    @property
+    def data_size(self):
+        return int(self.value(tag="datasize"))
+
+    @property
+    def audio_size(self):
+        return int(self.value(tag="audiosize"))
+
+    @property
+    def video_size(self):
+        return int(self.value(tag="videosize"))
+
+    @property
+    def file_size(self):
+        return int(self.value(tag="filesize"))
+
+    @property
+    def last_timestamp(self):
+        return float(self.value(tag="last_timestamp"))
+
+    @property
+    def last_video_frame_timestamp(self):
+        return float(self.value(tag="lastvideoframetimestamp"))
+
+    @property
+    def last_key_frame_timestamp(self):
+        return float(self.value(tag="lastkeyframetimestamp"))
+
+    @property
+    def last_key_frame_location(self):
+        return float(self.value(tag="lastkeyframelocation"))
+
+    @property
+    def key_frames(self):
+        return KeyFrame().all_from_xml(self.metadata)
+
+    def value(self, tag=None):
+        if tag is None:
+            raise NameError("Please provide tag should not be None")
+        target = self.metadata.first(tag)
+        return "".join(target.contents) if target else -1
+
+    def extract_meta(self, flv_path):
+        temp_file = tempfile.NamedTemporaryFile(mode="w+", delete=False)
+        os.system("yamdi -i {flv_path} -x {temp_file}".format(flv_path=flv_path, temp_file=temp_file.name))
+        temp = open(temp_file.name, "rb")
+        contents = temp.readlines()
+        temp.close()
+        temp_file.close()
+        os.unlink(temp_file.name)
+        return ''.join(contents)
+
+    def boolean(self, text):
+        if "true" in text: return True
+        elif "false" in text: return False