Running with Ruby

Tag: conversion (page 2 of 2)

Poprawne linki z Facebooka dla JWPlayera – wygasające linki – część I

Wstęp

Tutorial składa się z dwóch cześci:

  1. FacebookBot z wykorzystaniem Mechanize
  2. Cachowanie odpytań

Kod (nowszy i lepszy ;) ) dostępny na githubie.

Część z Was na pewno osadza swoje filmiki z konta Facebook także na swoich stronach www. Niektórzy robią to z pomocą odtwarzacza dostarczonego przez Facebooka, inni korzystają np. z JWPlayera. Niestety ostatnimi czasy, Facebook zauważył, że staje się bardzo fajną platformą hostingową dla filmów wszelakich (i to w jakości HD!). Zjadało im to (i zjada ;) ) gigantyczne ilości zasobów – przede wszystkim łącza. Jeśli osadzasz pliki wideo przez ich odtwarzacz – to nie ma problemu – FB ma z tego korzyści (mają swoje logo na filmiku, itd). Korzystając jednak z JWPlayera – nie uda ci się ta sztuczka. Linki bezpośrednie do plików MP4 są zmieniane co 24-36 godzin, w skutek czego umieszczanie ich “w” odtwarzaczu nie ma sensu. Na szczęście da się to bardzo łatwo rozwiązać, jak zawsze w… Rubym :)

Mechanize

Napiszemy prostego bota – który wchodzi na Facebooka, loguje się i sprawdza URL filmu. Warto dodać cacheowanie, tak aby nie odpytywać FB za każdym razem o to samo. Rozbudowę tego narzędzia pozostawię jednak Wam. Wracając do sedna sprawy. Aby napisać tego bota, skorzystamy z Mechanize. Mechanize jest biblioteką stworzoną do łatwej integracji ze stronami wszelakimi. Umożliwia przesyłanie formularzy, odwiedzanie stron, zapewnia obsługę cookies, ssl-a, itp, itd. To głównie dzięki niemu, będziemy mogli odświeżać linki do plików MP4 z FB.

Zanim jednak to zrobimy, musimy dodać do Mechanize pewną małą poprawkę. Domyślnie nie pozwala wyszukiwać formularzy po ID a nam taka metoda się przyda. Tak więc:

class Mechanize::Page
  def form_id(formId)
    formContents = (self/:form).find { |elem| elem['id'] == formId }
    if formContents then return Mechanize::Form.new(formContents) end
  end
end

Tyle :) A teraz pora na FacebookBota.

FacebookBot

Nasz bot będzie miał łącznie 4 metody:

  1. Initialize – inicjalizacja bota
  2. Login – logowanie do Facebooka
  3. Video_Url – pobranie URLa pliku wideo
  4. (priv) get_url – wyodrębnienie samego urla pliku ze strony z linkiem

Szkielet klasy będzie więc wyglądał tak:

require 'rubygems'
require 'mechanize'
require 'uri'
require 'cgi'
require 'time'

class FacebookBot
  # Strona główna Facebooka
  FB_URL = "http://www.facebook.com/"
  # Nasza przeglądarka i system :)
  USER_AGENT = 'Linux Firefox'

  def initialize(email, pass)
  end

  def login
  end

  def video_url(video_id)
  end

  private

  def get_url(url)
  end
end

Inicjalizacja naszego bota składa się z zapamiętania e-maila i hasła, utworzenia obiektu Mechanize do eksploracji FB oraz próby zalogowania na nasze konto:

  def initialize(email, pass)
    @email, @pass = email, pass

    @agent = Mechanize.new
    @agent.user_agent_alias = USER_AGENT

    @cookies = File.dirname(__FILE__) + "/../cookies-" + @email + ".yml"
    if (File.file?(@cookies))
      @agent.cookie_jar.load(@cookies)
    end

    self.login
  end

Wartym omówienia jest ten fragment:

    @cookies = File.dirname(__FILE__) + "/../cookies-" + @email + ".yml"
    if (File.file?(@cookies))
      @agent.cookie_jar.load(@cookies)
    end

Zmienna @cookies trzyma ciastka sesji od Facebooka na dysku. Dlaczego jest o katalog wyżej (../) niż sam plik? Ponieważ plik z botem wrzucimy do ‘lib/’ a plik z ciastkiem będziemy chcieli mieć w katalogu głównym naszej małej aplikacji. Tak więc, ustalamy sobie ścieżkę, sprawdzamy czy plik już istnieje i jeśli tak jest, to ładujemy jego zawartość do kontenera na ciacho – tak żeby można się było przedstawiać nim w Facebooku. Następnie podejmujemy próbę logowania.

Pora na logowanie:

  def login
    page = @agent.get(FB_URL)

    if (loginf = page.form_id("login_form"))
      loginf.set_fields(:email => @email, :pass => @pass)
      page = @agent.submit(loginf, loginf.buttons.first)
    end

    @agent.cookie_jar.save_as(@cookies)

    body = page.root.to_html
    @uid = %r{\\"user\\":(\d+),\\"hide\\"}.match(body)[1]
    @post_form_id = %r{<input type="hidden" id="post_form_id" name="post_form_id" value="([^"]+)}.match(body)[1]
  end

Logowanie przebiega w następujący sposób:

  1. Próbujemy przejść na stronę główną FB.
  2. Jeśli się to udało – tzn że nasze ciastka były i były prawidłowe
  3. Jeśli nie, to wypełniamy formularz logowania i klikamy na “Zaloguj”
  4. Zapamiętujemy uaktualnione ciastka
  5. Przechodzimy na stronę główną i zapamiętujemy id usera (UID)
  6. Zapamiętujemy klucz do przesyłania żądań typu POST

Prawda że proste? :)

Video_url:

def video_url(id)
    begin
      pa = @agent.get("#{FB_URL}video/video.php?v=#{id}")
      pr = pa.body
      get_url(pr)
    rescue
      "Niepoprawne ID pliku wideo lub wideo nie jest publiczne."
    end
end

Pobieramy stronę pliku wideo, wyciągamy link do MP4 i zwracamy. Jeśli coś pójdzie nie tak, to zwracamy info że wideo jest nieprawidłowe lub nie jest publiczne.

Samo parsowanie przebiega w sposób trochę “brutalny” – nie chciało mi się bawić i myśleć to wyciągnąłem tak niezbyt elegancko:

private

def get_url(url)
	url = url.scan(/addVariable\(\"highqual_src\",\s\"http.+\"\)/ix).first
	url = url.split(')').first.gsub('\u00253A', ':')
	url = url.gsub('\u00252F', '/')
	url = url.gsub('\u00253F', '?')
	url = url.gsub('\u00253D', '=')
	url = url.gsub('\u002526', '&')
	url = "http://#{url.split('http://')[1]}".split('"').first
	CGI.unescapeHTML(url)
end

Grunt że działa.
Oto jak działa całość:

  fb = FacebookBot.new('moj@mail.pl', 'moje_tajne_haslo')
  p  fb.video_url(ID_PLIKU_VIDEO)

Warto dodać do tego system cacheowanie, tak aby nie odpytywać FB za każdym razem o to samo!

Źródełko :)

W związku z szerokim zainteresowaniem, udostępniam prosty interfejs: www.fbc.mensfeld.pl. Linki do odcinków buduje się tak:

www.fbc.mensfeld.pl/ID-ODCINKA.mp4

Tutorial składa się z dwóch cześci:

  1. FacebookBot z wykorzystaniem Mechanize
  2. Cachowanie odpytań

Kod (nowszy i lepszy ;) ) dostępny na githubie.

PDF to JPG – konwersja wielu plików jednocześnie

Czasem dostajemy z jakiegoś źródła, pewną ilość PDFów które następnie musimy np. umieścić na stronie www. Problem pojawia się wtedy, gdy są to przykładowo skany dyplomów i fajniej byłoby mieć je w formacie JPG lub PNG.

Bardzo prosto można przekonwertować jeden plik. Wystarczy skorzystać z biblioteki Imagemagick, korzystając z poniższej składni:

convert -jakies_parametry plik.pdf wynik.jpg

Jak jednak, zrobić to samo dla wielu plików w wielu subkatalogach, a przy tym usunąć plik PDF?

Wystarczy wziąć do tego Rubiego! :) Oczywiście, można to zrobić w bashu, jednak różnicę między skorzystaniem z basha a rubiego, stanowi przenośność tego drugiego rozwiązania.

O ile skrypt basha odpalimy tylko na *nxie, o tyle mając interpreter Rubiego, konwerter odpalimy także pod windowsem.

Nasz skrypt będzie bardzo prosty. Będzie przyjmował dwa opcjonalne argumenty. Pierwszym będzie ścieżka do katalogu, od którego mamy zacząć szukać. Drugim będzie flaga odpowiadająca za ew. usuwanie plików PDF, po skończeniu konwersji.

Zanim jednak to zrobimy, musimy załączyć potrzebne nam biblioteki (oraz “magiczny” komentarz):

# coding: utf-8
require 'find'
require 'fileutils'

Teraz pora na nasze argumenty:

dir = ARGV[0] ? ARGV[0] : '.'
destroy_pdf = ARGV[1] ? ARGV[1] : false

jak nietrudno zauważyć, jeśli ich nie podany, to plików PDF będziemy szukać, w katalogu z którego wywołujemy skrypt, zaś PDFy nie będą po konwersji usuwane.

Następnie musimy sprawdzić, czy ścieżka do katalogu istnieje:

# Jeśli podana sciezka nie jest scieżką poprawną - wywal info o błędzie i
# zakończ skrypt
unless File.directory?(dir)
  puts "Podana ścieżka nie jest poprawna"
  exit 1
end

Następnie “przelatujemy” wszystkie pliki które mamy w katalogu (oraz subkatalogach), dokonujemy konwersji jeśli są to pliki PDF oraz ewentualnie usuwamy PDFa po konwersji:

# Przelatujemy każdy plik w katalogach i subkatalogach naszej sciezki
Find.find(dir) { |f|
  # Olej pliki które nie mają rozszerzenia pdf
  next unless f[f.length-4, 4] == '.pdf'
  # Przekonwertuje na jpg - z taką samą nazwą - tylko rozszerzenie inne
  new_name = "#{f[0, f.length-4]}.jpg"
  # Wyrzuć info o konwersji pliku
  puts "Konwertowanie pliku: #{f}"
  # Konwertujemy...
  system("convert -density 250x250 -quality 90 '#{f}' '#{new_name}'")
  # No i usun pdfa jesli tak podano
  if destroy_pdf
    puts "Usuwanie pliku: #{f}"
    File.delete(f)
  end
}

Warto zwrócić uwagę, że użycie samego convert, bez podania parametrów, sprawi że plik wynikowy będzie miał bardzo kiepską jakość. Aby tak nie było, korzystamy z parametru density, który odpowiada za jakość (DPI) pliku wynikowego. Ustawiamy też jakość na 90%, co w wypadku plików JPG sprawi że będzie bardzo dobrej jakości, przy nie aż tak wielkim rozmiarze.

To by było na tyle. Plik do pobrania: pdf_to_jpg.

Newerposts

Copyright © 2020 Running with Ruby

Theme by Anders NorenUp ↑