Pull to refresh

Конвертируем и загружаем на YouTube

Django *
Доброго времени суток!

Итак начнём. В статье я опишу краткий проект на django, который конвертирует/загружает видео-ролики на Ваш канал YouTube.


Пару слов об истории проблемы… Я работаю в небольшой региональной телевизионной компании, которой в один чудесный день захотелось, чтобы их контент люди могли наблюдать, не только сидя у голубых экранов, но и в YouTube. Т.к. у нас стоит вещательный сервер от Omneon, все материалы у нас готовятся в формате mov, а средний вес 15 минутной программы = 3,5Гб. Заливать файл такого размера утомительно и глупо, гораздо правильнее будет конвертировать ролики в mp4 (например) и выкладывать уже лёгкими на YouTube (благо качество от этого теряем мы не так много). Для примера: 3,5Гб сжимаются где-то в 100Мб приемлемого качества.

Статья не претендует на звание «открытие» и «прорыв года», она просто упорядочивает известные факты в удобную инструкцию.

1. Алгоритм


Для себя я руководствовался следующей последовательностью действий:
  • Смотрим в папку, где живут видео-ролики
  • Выбираем нужный
  • Вводим информацию для YT (название, описание, ключевые слова)
  • Ставим файл в очередь
  • В очереди последовательно идёт конвертирование роликов, потом загрузка их на YT
  • Как ролик загрузился, у меня высветился статус «uploaded»

Также мне захотелось добавить некое логирование, аля «кто / когда / что закачал».

2. Что необходимо?


Надеюсь django у Вас установлено, иначе дальше читать бессмысленно. Также потребуются следующие пакеты:
  • youtube-upload (за собой тянет он ещё и python-gdata)
  • ffmpeg (на ubuntu проставляется простой комбинацией apt-get install ffmpeg)


3. Собираем всё вместе


Сам люблю когда на рассказываемое хоть краем глаза можно посмотреть, потому вот финальный скриншот:


Буду рассказывать по пунктам с самого старта.
Первым делом стартуем новый проект: django-admin.py startproject YTupload

В новом проекте сразу правим settings.py под свои нужды и добавляем следующие поля:
ENCODE_DIR_FROM = '/путь_до_директории_в_которую_складываются_ролики_для_закачки_на_YT/'
ENCODE_DIR_TO = os.path.join(ROOT,'file_out') #временная папка куда будут складываться отконвертированные... у меня как видите это просто подпапка file_out в самом проекте
YT_LOGIN = 'login@gmail.com' #ваш логин от YT
YT_PASSWORD = 'password' #ваш пароль от YT


Выше я «заикался» о логировании действий. Вот для этого нам нужна следующая моделька.
создаём приложение django-admin.py startapp control
открываем models.py
# -*- coding: utf-8 -*-
from django.db import models
from django.contrib.auth.models import User

class Log(models.Model):
    filename = models.CharField(max_length=300)
    filesize = models.CharField(max_length=300)
    user = models.ForeignKey(User)
    created = models.DateTimeField()
    uploaded = models.DateTimeField(null=True, blank=True)
    link = models.CharField(max_length=300, blank=True)
    #youtube properties
    title = models.CharField(max_length=300)
    description = models.TextField()
    keywords = models.CharField(max_length=300)
    status = models.CharField(max_length=300, blank=True)
    
class People(models.Model):
    login = models.OneToOneField(User, primary_key=True)
    name = models.CharField(max_length=200)


переходим к views.py
# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.template import RequestContext
from django.shortcuts import get_object_or_404, get_list_or_404
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm

from control.models import *

import os, datetime, settings

# функция вывода статуса
def LogStatus(l):
    try: return Log.objects.filter(filename=l)[0].status
    except: return None

def index(request, template):
    if not request.user.is_authenticated():
        return HttpResponseRedirect('/enter/')
    loglist = Log.objects.order_by('-id')[:10]
    # смотрим в директорию
    listdir = os.listdir(settings.ENCODE_DIR_FROM)
    dictdir = [{'filename':None,'filesize':None, 'status':None} for i in range(len(listdir))]
    n = 0
    for l in listdir:
        dictdir[n]['filename'] = l
        dictdir[n]['filesize'] = os.path.getsize(os.path.join(settings.ENCODE_DIR_FROM,l))
        dictdir[n]['status'] = LogStatus(l)
        n+=1
    # действие по нажатию кнопки
    if request.method == 'POST':
        filename = request.POST['filename']
        filesize = request.POST['filesize']
        title = request.POST['title']
        description = request.POST['description']
        keywords = request.POST['keywords']
        # проверить не конвертируется ли ещё?
        if Log.objects.filter(filename=filename):
            return HttpResponse('already in query')
        else:
            # записать в Log, поставить статус "конвертируется"
            l = Log(filename=filename, filesize=filesize, user=User.objects.filter(id=int(request.user.id))[0], created=datetime.datetime.now(), title=title, description=description, keywords=keywords, status='in query')
            l.save()
            return HttpResponseRedirect('/')
    return render_to_response(template, {'dictdir':dictdir,
                                         'loglist':loglist,})
                                         
def exit(request):
    logout(request)
    return HttpResponseRedirect('/enter/')

def enter(request, template):
    authform = AuthenticationForm()
    if request.user.is_authenticated():
        return HttpResponseRedirect('/')
    if request.method == 'POST':
        authform = AuthenticationForm(data=request.POST)
        if authform.is_valid():
            login(request, authform.get_user())
            return HttpResponseRedirect('/')
    return render_to_response(template, {'authform': authform})


Код не сложный (если нужно будет, готов по подробнее рассказать), функция index выдаёт 2 списка: первый — какие файлы есть в папке, второй — какие файлы уже записаны в лог. Вывод этой функции виден на скриншоте выше… enter и exit — самая простая аутентификация.

Идём дальше: urls.py
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
import views, settings

urlpatterns = patterns('',
    (r'^admin/', include(admin.site.urls)),
    (r'^$', views.index, {'template': 'index.html'}),
    (r'^enter/$', views.enter, {'template': 'enter.html'}),
    (r'^exit/$', views.exit),
    (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),                     
)

Здесь всё просто. Зашёл, авторизовался, закачал!

Финальный аккорд — это converting.py, который и делает всю работу.
# -*- coding: utf-8 -*-
import settings, os, datetime
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from control.models import *

if Log.objects.filter(status='converting'):
    quit()
else:
    try:
        file = Log.objects.filter(status='in query')[0]
    except:
        quit()
    file.status = 'converting'
    file.save()
    # новое имя файла
    basename, extension = os.path.splitext(file.filename)
    newfilename = basename + '.flv'
    os.system('ffmpeg -i ' + os.path.join(settings.ENCODE_DIR_FROM, file.filename) + ' -ar 22050 -vb 1500kbits/s ' + os.path.join(settings.ENCODE_DIR_TO, newfilename))
    file.status = 'uploading'
    file.save()
    os.system('youtube-upload --email=' + settings.YT_LOGIN + ' --password=' + settings.YT_PASSWORD + ' --title="' + file.title.encode('utf-8') + '" --description="' + file.description.encode('utf-8') + '" --category="News" --keywords="' + file.keywords.encode('utf-8') + '" ' + os.path.join(settings.ENCODE_DIR_TO, newfilename).encode("utf-8"))
    file.status = 'uploaded'
    file.uploaded = datetime.datetime.now()
    file.save()


Этот скрипт у меня запускается через crontab. Смысл прост: берём первый попавшийся файл со статусом «in query» (в очереди), конвертируем (меняем статус на converting), загружаем (меняем статус на uploading), загрузили (статус = uploaded).
crontab:
*/1 * * * * python /var/www/YTupload/converting.py &> /dev/null

Ну и собственно дарю html-код шаблона:
enter.html
<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<title>Вход | YTUpload | 1vank1n.habrahabr.ru</title>
<style type="text/css">
html, body {
	font-size: 14px;
	font-family: Georgia, "Times New Roman", Times, serif;
	height: 100%;
	width: 100%;
	margin: 0;
	padding: 0;
}
#table-center {
	height: 100%;
	width: 100%;
}

#form-auth {
	margin-bottom: 0px;
	margin-top: 0px;
	margin-right: auto;
	margin-left: auto;
	height: 100px;
	width: 330px;
}

#form-auth table td {
	padding: 5px;
}

#form-auth input {
	background-color: white;
	border-color: #cccccc;
	border-style: solid;
	border-width: 1px;
	padding: 5px;
	display: block;
	margin: 0 auto;
}
</style>

</head>
<body>
<table id="table-center">
<tr><td>
    <form id="form-auth" action="" method="POST">{% csrf_token %}
    <table >
    	{{ authform.as_table }}
    	<input type="hidden" name="formname" value="auth">
    </table>
    	<input type="submit" value="Вход">
    </form>
</td></tr>
</table>
</body>
</html>


index.html
<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<title>YTUpload | 1vank1n.habrahabr.ru</title>
	
	<script type="text/javascript" src="/media/jquery-1.6.1.min.js"></script>
	<script type="text/javascript">
        $(function(){
            $('#choose_file a').click(function(){
                $('#file_properties').fadeIn();
                $('input[name="filename"]').attr('value', $(this).text());
                $('input[name="filesize"]').attr('value', $(this).next('span').text());
            });
        });
	</script>
	<style type="text/css">
	   body {width:1000px;margin:0 auto;position: relative;font-size:11pt;}
	   #file_properties {display: none;}
	   #choose_file {width: 490px; float: left; height: 500px; overflow-y: auto;}
	   #loglist, #loglist table {width: 500px; float:right;}
	   #loglist td {border: 1px solid #555;margin: 0;padding: 5px;}
	   #exit {display: block; position: absolute; top:0; right:0; padding: 10px; background: #ddf; color: #000; text-decoration: none;}
	   input, textarea, button {border: 1px solid #ccc; padding:5px;}
	</style>
</head>
<body>
    <a id="exit" href="/exit/">Выход</a>
    <div id="loglist">
        <h1>Отчёт</h1>
        <table cellpadding="0" cellspacing="0">
            <tr>
                <td>Имя файла</td>
                <td>Размер</td>
                <td>Статус</td>
            </tr>
        {% for l in loglist %}
            <tr>
                <td>{{ l.filename }}</td>
                <td>{{ l.filesize }}</td>
                <td>{{ l.status }}</td>
            </tr>
        {% endfor %}
        </table>
    </div>
    <div id="choose_file">
	   <h1>1. Выберите файл:</h1>
	   <p>Используйте в имени файла только латинские буквы, без пробелов и всяких там символов (+,-,%,',",^,&)!!!</p>
	   {% for d in dictdir %}
	   <li>{% if not d.status %}<a value="{{ d.filename }}" href="#">{% endif %}{{ d.filename }}</a> | <span>{{ d.filesize|filesizeformat }}</span></li>
	   {% endfor %}
	</div>
	<div style="height:10px;width:100%;clear:both;"></div>
	<div id="file_properties">
	      <h1>2. Описание файла:</h1>
	      <table>
	      <form method="post" name="uploadform">
	      <tr><td>Файл:</td><td><input type="text" name="filename" readonly /> <input type="text" name="filesize" hidden></td></tr>
	      <tr><td>Название:</td><td><input type="text" name="title" /></td></tr>
	      <tr><td>Описание:</td><td><textarea name="description"></textarea></td></tr>
	      <!-- <tr><td>Категория:</td><td><select></select></td></tr> -->
	      <tr><td>Ключевые слова:</td><td><input type="text" name="keywords" /></td></tr>
	      <tr><td></td><td><input type="submit" value="3. Загрузить на YouTube" /></td></tr>
	      </table>
	      </form>
    </div>
</body>
</html>


4. Вместо финала


Надеюсь кому-то это поможет / сэкономит время / или просто повеселит. Представленный мной вариант можно доработать (может выложить на github?), слава богу кучу всего ещё можно поправить, например какие я вижу варианты развития:
  • добавить выбор в какой playlist грузить
  • прогрессбар загрузки
  • уведомления о загрузке
  • возможность выбора в какой канал видео загружать
  • возможность выбора качества (битрейт конвертации)
  • и наверняка ещё что-то...


Спасибо за внимание!

P.S. код далёк от совершенства, будет «шлифоваться». Спасибо Evgeny Bespaly за советы по оптимизации, обязательно воспользуюсь.

UPD: проект на github = https://github.com/1vank1n/YTupload/, буду рад всем кто поможет довести его до ума!
Tags:
Hubs:
Total votes 38: ↑37 and ↓1 +36
Views 2.7K
Comments Comments 22