search
HomeBackend DevelopmentPython Tutorial用Python的Django框架完成视频处理任务的教程

Stickyworld 的网页应用已经支持视频拨放一段时间,但都是通过YouTube的嵌入模式实现。我们开始提供新的版本支持视频操作,可以让我们的用户不用受制于YouTube的服务。

我过去曾经参与过一个项目,客户需要视频转码功能,这实在不是个容易达成的需求。需要大量的读取每一个视频、音讯与视频容器的格式再输出符合网页使用与喜好的视频格式。

考虑到这一点,我们决定将转码的工作交给 Encoding.com 。这个网站可以免费让你编码1GB大小的视频,超过1GB容量的文件将采取分级计价收费。

开发的代码如下,我上传了一个178KB容量的两秒视频来测试代码是否成功运作。当测试过程没有发生任何的例外错误后,我继续测试其它更大的外部文件。

 
阶段一:用户上传视频文件

现在这的新的代码段提供了一个基于 HTML5且可以快速上手的 的上传机制。用CoffeeScript撰写的代码,可以从客户端上传文件到服务器端。
 

$scope.upload_slide = (upload_slide_form) ->
  file = document.getElementById("slide_file").files[0]
  reader = new FileReader()
  reader.readAsDataURL file
  reader.onload = (event) ->
   result = event.target.result
   fileName = document.getElementById("slide_file").files[0].name
   $.post "/world/upload_slide",
    data: result
    name: fileName
    room_id: $scope.room.id
    (response_data) ->
     if response_data.success? is not yes
      console.error "There was an error uploading the file", response_data
     else
      console.log "Upload successful", response_data
  reader.onloadstart = ->
   console.log "onloadstart"
  reader.onprogress = (event) ->
   console.log "onprogress", event.total, event.loaded, (event.loaded / event.total) * 100
  reader.onabort = ->
   console.log "onabort"
  reader.onerror = ->
   console.log "onerror"
  reader.onloadend = (event) ->
   console.log "onloadend", event

最好可以通过 (“slide_file”).files 且经由独立的POST上传每个文件,而不是由一个POST需求上传所有文件。稍后我们会解释这点。

 
阶段二:验证并上传至 Amazon S3

后端我们运行了Django与RabbitMQ。主要的模块如下:
 

$ pip install 'Django>=1.5.2' 'django-celery>=3.0.21' \
  'django-storages>=1.1.8' 'lxml>=3.2.3' 'python-magic>=0.4.3'

我建立了两个模块:SlideUploadQueue 用来储存每一次上传的数据,SlideVideoMedia 则是用来储存每个要上传影片的数据。
 
class SlideUploadQueue(models.Model):
  created_by = models.ForeignKey(User)
  created_time = models.DateTimeField(db_index=True)
  original_file = models.FileField(
    upload_to=filename_sanitiser, blank=True, default='')
  media_type = models.ForeignKey(MediaType)
  encoding_com_tracking_code = models.CharField(
    default='', max_length=24, blank=True)
 
  STATUS_AWAITING_DATA = 0
  STATUS_AWAITING_PROCESSING = 1
  STATUS_PROCESSING = 2
  STATUS_AWAITING_3RD_PARTY_PROCESSING = 5
  STATUS_FINISHED = 3
  STATUS_FAILED = 4
 
  STATUS_LIST = (
    (STATUS_AWAITING_DATA, 'Awaiting Data'),
    (STATUS_AWAITING_PROCESSING, 'Awaiting processing'),
    (STATUS_PROCESSING, 'Processing'),
    (STATUS_AWAITING_3RD_PARTY_PROCESSING,
      'Awaiting 3rd-party processing'),
    (STATUS_FINISHED, 'Finished'),
    (STATUS_FAILED, 'Failed'),
  )
 
  status = models.PositiveSmallIntegerField(
    default=STATUS_AWAITING_DATA, choices=STATUS_LIST)
 
  class Meta:
    verbose_name = 'Slide'
    verbose_name_plural = 'Slide upload queue'
 
  def save(self, *args, **kwargs):
    if not self.created_time:
      self.created_time = \
        datetime.utcnow().replace(tzinfo=pytz.utc)
 
    return super(SlideUploadQueue, self).save(*args, **kwargs)
 
  def __unicode__(self):
    if self.id is None:
      return 'new <SlideUploadQueue>'
    return '<SlideUploadQueue> %d' % self.id
 
class SlideVideoMedia(models.Model):
  converted_file = models.FileField(
    upload_to=filename_sanitiser, blank=True, default='')
 
  FORMAT_MP4 = 0
  FORMAT_WEBM = 1
  FORMAT_OGG = 2
  FORMAT_FL9 = 3
  FORMAT_THUMB = 4
 
  supported_formats = (
    (FORMAT_MP4, 'MPEG 4'),
    (FORMAT_WEBM, 'WebM'),
    (FORMAT_OGG, 'OGG'),
    (FORMAT_FL9, 'Flash 9 Video'),
    (FORMAT_THUMB, 'Thumbnail'),
  )
 
  mime_types = (
    (FORMAT_MP4, 'video/mp4'),
    (FORMAT_WEBM, 'video/webm'),
    (FORMAT_OGG, 'video/ogg'),
    (FORMAT_FL9, 'video/mp4'),
    (FORMAT_THUMB, 'image/jpeg'),
  )
 
  format = models.PositiveSmallIntegerField(
    default=FORMAT_MP4, choices=supported_formats)
 
  class Meta:
    verbose_name = 'Slide video'
    verbose_name_plural = 'Slide videos'
 
  def __unicode__(self):
    if self.id is None:
      return 'new <SlideVideoMedia>'
    return '<SlideVideoMedia> %d' % self.id

我们的模块皆使用 filename_sanitiser。FileField 自动的将文件名调整成 /. 格式。整理每个文件名并确保其独一性。我们采用了有时效性签署的网址列让我们可以掌控哪些使用者在使用我们的服务,使用了多久。
 

def filename_sanitiser(instance, filename):
  folder = instance.__class__.__name__.lower()
  ext = 'jpg'
 
  if '.' in filename:
    t_ext = filename.split('.')[-1].strip().lower()
 
    if t_ext != '':
      ext = t_ext
 
  return '%s/%s.%s' % (folder, str(uuid.uuid4()), ext)

拿来测试的文件 testing.mov 将会转换成以下网址:https://our-bucket.s3.amazonaws.com/slideuploadqueue/3fe27193-e87f-4244-9aa2-66409f70ebd3.mov 并经由Django Storages 模块上传。

我们通过 Magic 验证从使用者端浏览器上传的文件。Magic可以从文件内容侦测是何种类型的文件。
 

@verify_auth_token
@return_json
def upload_slide(request):
  file_data = request.POST.get('data', '')
  file_data = base64.b64decode(file_data.split(';base64,')[1])
  description = magic.from_buffer(file_data)

如果文件类型符合MPEG v4 系统或是Apple QuickTime 电影,我们就知道该文件转码不会有太大问题。如果格式不是上述所提的几种,我们会标志给用户知悉。
接着,我们将通过SlideUploadQueue 模块将视频储存到队列并发送一个需求给 RabbitMQ。因为我们使用了Django Storages 模块,文件将自动被上传到 Amazon S3。
 

slide_upload = SlideUploadQueue()
...
slide_upload.status = SlideUploadQueue.STATUS_AWAITING_PROCESSING
slide_upload.save()
slide_upload.original_file.\
  save('anything.%s' % file_ext, ContentFile(file_data))
slide_upload.save()
 
task = ConvertRawSlideToSlide()
task.delay(slide_upload)

阶段3:发送视频到第三方.

RabbitMQ 将控管 task.delay(slide_upload) 的呼叫。
我们现在只需要发送视频档网址与输出格式给Encoding.com。该网站会回复我们一个工作码让我们检查视频转码的进度。

class ConvertRawSlideToSlide(Task):
  queue = 'backend_convert_raw_slides'
  ...
  def _handle_video(self, slide_upload):
    mp4 = {
      'output': 'mp4',
      'size': '320x240',
      'bitrate': '256k',
      'audio_bitrate': '64k',
      'audio_channels_number': '2',
      'keep_aspect_ratio': 'yes',
      'video_codec': 'mpeg4',
      'profile': 'main',
      'vcodecparameters': 'no',
      'audio_codec': 'libfaac',
      'two_pass': 'no',
      'cbr': 'no',
      'deinterlacing': 'no',
      'keyframe': '300',
      'audio_volume': '100',
      'file_extension': 'mp4',
      'hint': 'no',
    }
 
    webm = {
      'output': 'webm',
      'size': '320x240',
      'bitrate': '256k',
      'audio_bitrate': '64k',
      'audio_sample_rate': '44100',
      'audio_channels_number': '2',
      'keep_aspect_ratio': 'yes',
      'video_codec': 'libvpx',
      'profile': 'baseline',
      'vcodecparameters': 'no',
      'audio_codec': 'libvorbis',
      'two_pass': 'no',
      'cbr': 'no',
      'deinterlacing': 'no',
      'keyframe': '300',
      'audio_volume': '100',
      'preset': '6',
      'file_extension': 'webm',
      'acbr': 'no',
    }
 
    ogg = {
      'output': 'ogg',
      'size': '320x240',
      'bitrate': '256k',
      'audio_bitrate': '64k',
      'audio_sample_rate': '44100',
      'audio_channels_number': '2',
      'keep_aspect_ratio': 'yes',
      'video_codec': 'libtheora',
      'profile': 'baseline',
      'vcodecparameters': 'no',
      'audio_codec': 'libvorbis',
      'two_pass': 'no',
      'cbr': 'no',
      'deinterlacing': 'no',
      'keyframe': '300',
      'audio_volume': '100',
      'file_extension': 'ogg',
      'acbr': 'no',
    }
 
    flv = {
      'output': 'fl9',
      'size': '320x240',
      'bitrate': '256k',
      'audio_bitrate': '64k',
      'audio_channels_number': '2',
      'keep_aspect_ratio': 'yes',
      'video_codec': 'libx264',
      'profile': 'high',
      'vcodecparameters': 'no',
      'audio_codec': 'libfaac',
      'two_pass': 'no',
      'cbr': 'no',
      'deinterlacing': 'no',
      'keyframe': '300',
      'audio_volume': '100',
      'file_extension': 'mp4',
    }
 
    thumbnail = {
      'output': 'thumbnail',
      'time': '5',
      'video_codec': 'mjpeg',
      'keep_aspect_ratio': 'yes',
      'file_extension': 'jpg',
    }
 
    encoder = Encoding(settings.ENCODING_API_USER_ID,
      settings.ENCODING_API_USER_KEY)
    resp = encoder.add_media(source=[slide_upload.original_file.url],
      formats=[mp4, webm, ogg, flv, thumbnail])
 
    media_id = None
 
    if resp is not None and resp.get('response') is not None:
      media_id = resp.get('response').get('MediaID')
 
    if media_id is None:
      slide_upload.status = SlideUploadQueue.STATUS_FAILED
      slide_upload.save()
      log.error('Unable to communicate with encoding.com')
      return False
 
    slide_upload.encoding_com_tracking_code = media_id
    slide_upload.status = \
      SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING
    slide_upload.save()
    return True

Encoding.com 推荐一些堪用的Python程序,可用来与它们的服务沟通。我修改了模块一些地方,但还需要修改一些功能才能达到我满意的状态。以下是修改过后目前正在使用的程序代码:

import httplib
from lxml import etree
import urllib
from xml.parsers.expat import ExpatError
import xmltodict
 
ENCODING_API_URL = 'manage.encoding.com:80'
 
class Encoding(object):
 
  def __init__(self, userid, userkey, url=ENCODING_API_URL):
    self.url = url
    self.userid = userid
    self.userkey = userkey
 
  def get_media_info(self, action='GetMediaInfo', ids=[],
    headers={'Content-Type': 'application/x-www-form-urlencoded'}):
    query = etree.Element('query')
 
    nodes = {
      'userid': self.userid,
      'userkey': self.userkey,
      'action': action,
      'mediaid': ','.join(ids),
    }
 
    query = self._build_tree(etree.Element('query'), nodes)
    results = self._execute_request(query, headers)
 
    return self._parse_results(results)
 
  def get_status(self, action='GetStatus', ids=[], extended='no',
    headers={'Content-Type': 'application/x-www-form-urlencoded'}):
    query = etree.Element('query')
 
    nodes = {
      'userid': self.userid,
      'userkey': self.userkey,
      'action': action,
      'extended': extended,
      'mediaid': ','.join(ids),
    }
 
    query = self._build_tree(etree.Element('query'), nodes)
    results = self._execute_request(query, headers)
 
    return self._parse_results(results)
 
  def add_media(self, action='AddMedia', source=[], notify='', formats=[],
    instant='no',
    headers={'Content-Type': 'application/x-www-form-urlencoded'}):
    query = etree.Element('query')
 
    nodes = {
      'userid': self.userid,
      'userkey': self.userkey,
      'action': action,
      'source': source,
      'notify': notify,
      'instant': instant,
    }
 
    query = self._build_tree(etree.Element('query'), nodes)
 
    for format in formats:
      format_node = self._build_tree(etree.Element('format'), format)
      query.append(format_node)
 
    results = self._execute_request(query, headers)
    return self._parse_results(results)
 
  def _build_tree(self, node, data):
    for k, v in data.items():
      if isinstance(v, list):
        for item in v:
          element = etree.Element(k)
          element.text = item
          node.append(element)
      else:
        element = etree.Element(k)
        element.text = v
        node.append(element)
 
    return node
 
  def _execute_request(self, xml, headers, path='', method='POST'):
    params = urllib.urlencode({'xml': etree.tostring(xml)})
 
    conn = httplib.HTTPConnection(self.url)
    conn.request(method, path, params, headers)
    response = conn.getresponse()
    data = response.read()
    conn.close()
    return data
 
  def _parse_results(self, results):
    try:
      return xmltodict.parse(results)
    except ExpatError, e:
      print 'Error parsing encoding.com response'
      print e
      return None

其他待完成事项包括通过HTTPS-only (加密联机) 使用Encoding.com 严谨的SSL验证,还有一些单元测试。
阶段4:下载所有新的视频档格式

我们有个定期执行的程序,通过RabbitMQ每15秒检查视频转码的进度:
 

class CheckUpOnThirdParties(PeriodicTask):
  run_every = timedelta(seconds=settings.THIRD_PARTY_CHECK_UP_INTERVAL)
  ...
  def _handle_encoding_com(self, slides):
    format_lookup = {
      'mp4': SlideVideoMedia.FORMAT_MP4,
      'webm': SlideVideoMedia.FORMAT_WEBM,
      'ogg': SlideVideoMedia.FORMAT_OGG,
      'fl9': SlideVideoMedia.FORMAT_FL9,
      'thumbnail': SlideVideoMedia.FORMAT_THUMB,
    }
 
    encoder = Encoding(settings.ENCODING_API_USER_ID,
      settings.ENCODING_API_USER_KEY)
 
    job_ids = [item.encoding_com_tracking_code for item in slides]
    resp = encoder.get_status(ids=job_ids)
 
    if resp is None:
      log.error('Unable to check up on encoding.com')
      return False

检查Encoding.com的响应来验证每个部分是否正确以利我们继续下去。

if resp.get('response') is None:
  log.error('Unable to get response node from encoding.com')
  return False
 
resp_id = resp.get('response').get('id')
 
if resp_id is None:
  log.error('Unable to get media id from encoding.com')
  return False
 
slide = SlideUploadQueue.objects.filter(
  status=SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING,
  encoding_com_tracking_code=resp_id)
 
if len(slide) != 1:
  log.error('Unable to find a single record for %s' % resp_id)
  return False
 
resp_status = resp.get('response').get('status')
 
if resp_status is None:
  log.error('Unable to get status from encoding.com')
  return False
 
if resp_status != u'Finished':
  log.debug("%s isn't finished, will check back later" % resp_id)
  return True
 
formats = resp.get('response').get('format')
 
if formats is None:
  log.error("No output formats were found. Something's wrong.")
  return False
 
for format in formats:
  try:
    assert format.get('status') == u'Finished', \
    "%s is not finished. Something's wrong." % format.get('id')
 
    output = format.get('output')
    assert output in ('mp4', 'webm', 'ogg', 'fl9',
      'thumbnail'), 'Unknown output format %s' % output
 
    s3_dest = format.get('s3_destination')
    assert 'http://encoding.com.result.s3.amazonaws.com/'\
      in s3_dest, 'Suspicious S3 url: %s' % s3_dest
 
    https_link = \
      'https://s3.amazonaws.com/encoding.com.result/%s' %\
      s3_dest.split('/')[-1]
    file_ext = https_link.split('.')[-1].strip()
 
    assert len(file_ext) > 0,\
      'Unable to get file extension from %s' % https_link
 
    count = SlideVideoMedia.objects.filter(slide_upload=slide,
      format=format_lookup[output]).count()
 
    if count != 0:
      print 'There is already a %s file for this slide' % output
      continue
 
    content = self.download_content(https_link)
 
    assert content is not None,\
      'There is no content for %s' % format.get('id')
  except AssertionError, e:
    log.error('A format did not pass all assertions: %s' % e)
    continue

到这里我们已确认所有事项皆正常,所以我们可以储存所有的视频档了:
 

media = SlideVideoMedia()
media.format = format_lookup[output]
media.converted_file.save('blah.%s' % file_ext, ContentFile(content))
media.save()

阶段5:经由HTML5播放视频档

在我们的前端网页已经新增了一个有HTML5的影像单元的网页。并采用对每个浏览器都有最佳支持的video.js来显示视频。
 

&#63; bower install video.js
bower caching git://github.com/videojs/video.js-component.git
bower cloning git://github.com/videojs/video.js-component.git
bower fetching video.js
bower checking out video.js#v4.0.3
bower copying /home/mark/.bower/cache/video.js/5ab058cd60c5615aa38e8e706cd0f307
bower installing video.js#4.0.3

在我们的首页有包含其他相依的文件:
 

!!! 5
html(lang="en", class="no-js")
 head
  meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
  ...
  link(rel='stylesheet', type='text/css', href='/components/video-js-4.1.0/video-js.css')
  script(type='text/javascript', src='/components/video-js-4.1.0/video.js')

在Angular.js/JADE-based 框架下的模块,我们引入

#main.span12
  video#example_video_1.video-js.vjs-default-skin(controls, preload="auto", width="640", height="264", poster="{{video_thumbnail}}", data-setup='{"example_option":true}', ng-show="videos")
    source(ng-repeat="video in videos", src="{{video.src}}", type="{{video.type}}")

还会显示出我们转换的每个视频文件格式,并使用在标签。Video.js 会根据使用者使用的浏览器决定播放哪种格式的视频。

我们仍然有许多工作需要完成,建立单元测试与加强和Encoding.com服务沟通的程序。如果你对这些工作感兴趣请与我连络。

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
详细讲解Python之Seaborn(数据可视化)详细讲解Python之Seaborn(数据可视化)Apr 21, 2022 pm 06:08 PM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于Seaborn的相关问题,包括了数据可视化处理的散点图、折线图、条形图等等内容,下面一起来看一下,希望对大家有帮助。

详细了解Python进程池与进程锁详细了解Python进程池与进程锁May 10, 2022 pm 06:11 PM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于进程池与进程锁的相关问题,包括进程池的创建模块,进程池函数等等内容,下面一起来看一下,希望对大家有帮助。

Python自动化实践之筛选简历Python自动化实践之筛选简历Jun 07, 2022 pm 06:59 PM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于简历筛选的相关问题,包括了定义 ReadDoc 类用以读取 word 文件以及定义 search_word 函数用以筛选的相关内容,下面一起来看一下,希望对大家有帮助。

归纳总结Python标准库归纳总结Python标准库May 03, 2022 am 09:00 AM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于标准库总结的相关问题,下面一起来看一下,希望对大家有帮助。

Python数据类型详解之字符串、数字Python数据类型详解之字符串、数字Apr 27, 2022 pm 07:27 PM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于数据类型之字符串、数字的相关问题,下面一起来看一下,希望对大家有帮助。

分享10款高效的VSCode插件,总有一款能够惊艳到你!!分享10款高效的VSCode插件,总有一款能够惊艳到你!!Mar 09, 2021 am 10:15 AM

VS Code的确是一款非常热门、有强大用户基础的一款开发工具。本文给大家介绍一下10款高效、好用的插件,能够让原本单薄的VS Code如虎添翼,开发效率顿时提升到一个新的阶段。

详细介绍python的numpy模块详细介绍python的numpy模块May 19, 2022 am 11:43 AM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于numpy模块的相关问题,Numpy是Numerical Python extensions的缩写,字面意思是Python数值计算扩展,下面一起来看一下,希望对大家有帮助。

python中文是什么意思python中文是什么意思Jun 24, 2019 pm 02:22 PM

pythn的中文意思是巨蟒、蟒蛇。1989年圣诞节期间,Guido van Rossum在家闲的没事干,为了跟朋友庆祝圣诞节,决定发明一种全新的脚本语言。他很喜欢一个肥皂剧叫Monty Python,所以便把这门语言叫做python。

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Tools

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Integrate Eclipse with SAP NetWeaver application server.

PhpStorm Mac version

PhpStorm Mac version

The latest (2018.2.1) professional PHP integrated development tool

DVWA

DVWA

Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

SublimeText3 English version

SublimeText3 English version

Recommended: Win version, supports code prompts!

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment