Umgang mit Images in RefineryCMS Nachtrag

There was a time, when I shared a blog. This post was written by Stephan. See all blogpost from him or stalk him on github.


RefineryCMS in der Rails 3 Version lieferte einige Änderungen betreffend des Umgangs mit Bildern und Dateien. Es wurde nun auf die Verwendung von Dragonfly gesetzt, die als Rack Middleware in der Railsanwendungen betrieben wird.

Folgend eine kleine Gedankenstütze für die Verwendung in den Active Record Models, um Verbindungen mit den neuen Image und Resource Plugins zu erreichen:

Refinery Engine benutzt eine Bilddatei: z.B: eine Galerie hat ein Titelbild

Model:

class Gallery < ActiveRecord::Base
 belongs_to :image
 #Galerie-Migration muss eine Spalte image_id (Rubykonvention) besitzen oder kann mit :foreign_key => :eigener_name angepasst werden
 #belongs_to image, :foreign_key => :title_image
end

View:

     <%= form_for([:admin, @gallery]) do |f| %>
     #andere Modelattribute z.B. Titel
        <div class='field'>
          <%= f.label :image_id -%>
          <%= render :partial => "/shared/admin/image_picker", :locals => {
            :f => f,
            :field => :image_id,
            :image => @gallery.image,
            :toggle_image_display => false
          } %>
          #image_picker speichert die id des ausgewählten Bildes im Feld image_id
        </div>
    <% end %>

Refinery Engine benutzt mehrere Bilddateien: z.B: ein Projekt hat ein Titelbild und ein Statusbild

Model:

    class Project < ActiveRecord::Base
     belongs_to :title_image, :class_name => "Image"
     belongs_to :status_image, :class_name => "Image"
     #Projekt-Migration muss die Spalten title_image_id und status_image_id (Rubykonvention) besitzen oder kann mit :foreign_key => :eigener_name angepasst werden siehe Beispiel zuvor
    end

View:

     <%= form_for([:admin, @project]) do |f| %>
     #andere Modelattribute z.B. Titel
        <div class='field'>
          <%= f.label :title_image_id -%>
          <%= render :partial => "/shared/admin/image_picker", :locals => {
            :f => f,
            :field => :title_image_id,
            :image => @project.title_image,
            :toggle_image_display => false
          } %>
          # image_picker speichert die id des ausgewählten Titelbildes im Feld title_image_id
        </div>

        <div class='field'>
          <%= f.label :status_image_id -%>
          <%= render :partial => "/shared/admin/image_picker", :locals => {
            :f => f,
            :field => :status_image_id,
            :image => @project.status_image,
            :toggle_image_display => false
          } %>
          # image_picker speichert die id des ausgewählten Statusbildes im Feld status_image_id
        </div>
    <% end %>

Will man eine Galerie mit vielen Bildern abbilden, muss man eine has_many Relation erzeugen

Model:

    #gallery.rb
    class Gallery < ActiveRecord::Base
      #Verknüpfungstabelle zur Join-Tabelle
      has_many :galleryitems, :dependent => :destroy, :order => "galleryitems.position ASC"
      #Verknüpfungstabelle zur Image Engine von RefineryCMS
      has_many :images :through => :galleryitems
    end

    #galleryitem.rb
    class Galleryitem < ActiveRecord::Base
      belongs_to :image
      belongs_to :gallery
      #Galleryitem-Migration muss die Spalten image_id und gallery_id (Rubykonvention) besitzen oder kann mit :foreign_key => :eigener_name angepasst werden
    end

Generell finde ich die gesamte Logik, die Bilder- und Datei-Uploads in RefineryCMS in einer Datenbank abzuspeichern nicht sehr komfortabel. Es führt zu Problemen bei Änderung von Dateinamen und lässt das Hochladen von Bildern zu einer sehr langwierigen Angelegenheit werden (Refinery 0.9.9 wird bereits HTML5 Multiupload ermöglichen). Ich muss noch einige Recherchen beenden (Rails Metal, Rack und vor allem Dragonfly) und vielleicht kann ich bald ein neues Filebrowser-Plugin für RefineryCMS vorstellen, bei dem ich mich konzeptionell an den Filebrowser von Typo3 orientiert habe.

Ich will nicht zu viel versprechen, aber demnächst mehr von mir

Gruß skeller1

Rails 3 lib Ordner bei jeder Änderung des Quellcodes neu laden

There was a time, when I shared a blog. This post was written by Stephan. See all blogpost from him or stalk him on github.


Bei Code-Änderungen im Rails lib Ordner muss zwingenderweise immer der Server neugestartet werden. Dies ist bei der aktiven Entwicklung natürlich sehr störend. Mit folgender initializer-Datei kannst du dieses Verhalten unterdrücken und es werden bei jedem Request die Dateien im lib-Ordner neu eingelesen. Lege dafür eine neue Initializer-Datei unter config/initalizers an. Der Dateinamen kann beliebig gewählt werden.

if Rails.env == "development"
  lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/**/*"], true) do
    Rails.application.reload_routes!
  end

  ActionDispatch::Callbacks.to_prepare do
    lib_reloader.execute_if_updated
  end
end

Das sieht zwar jetzt alles gut aus, doch wir müssen in der config/application.rb noch das automatische Laden der Libs aktivieren.

config.autoload_paths += %W( #{config.root}/lib )

Ein Dank geht an den User pbhogan, der auf StackOverflow die Lösung veröffentlicht hat (Link Original Beitrag).

Anmerkung:

Diese Lösung funktioniert leider nicht im lib Ordner von Rails Engines. Dort müssen sich die Entwickler noch etwas einfallen lassen.

Viel Spaß

Ein mehrspaltiges Ergebnis aus einer Spalte generieren (MySQL)

Das Problem:

Eine Datenbank, in der die Daten alle 5 Minuten mit Datum/Zeit gespeichert werden. Die Daten sollen in einem SQL-Ergebnis so dargestellt werden, dass von allen bis jetzt vorhandenen Jahren eine Spalte erzeugt wird um (aufsummierte) Monatswerte vergleichen zu können.

Die Lösung:

SELECT * FROM
(
  SELECT
    Extract(month from `date`) as monat,
    max(`E-Total`) - min(`E-Total`) as diff2009
  FROM
    archiv
  WHERE
    Extract(year from `date`) = 2009
  GROUP BY monat
  ORDER BY monat
) as t2009
NATURAL LEFT JOIN
(
  SELECT
    Extract(month from `date`) as monat,
    max(`E-Total`) - min(`E-Total`) as diff2010
  FROM
    archiv
  WHERE
    Extract(year from `date`) = 2010
  GROUP BY monat
  ORDER BY monat
) as t2010
NATURAL LEFT JOIN
(
  SELECT
    Extract(month from `date`) as monat,
    max(`E-Total`) - min(`E-Total`) as diff2011
  FROM
    archiv
  WHERE
    Extract(year from `date`) = 2011
  GROUP BY monat
  ORDER BY monat
) as t2011

Das Ergebnis:

+-------+----------+----------+----------+
| monat | diff2009 | diff2010 | diff2011 |
+-------+----------+----------+----------+
|     1 |   180.00 |    23.66 |     2.27 |
|     2 |   136.00 |   146.99 |     NULL |
|     3 |   279.00 |   355.02 |     NULL |
|     4 |   475.00 |   476.27 |     NULL |
|     5 |   477.00 |   309.44 |     NULL |
|     6 |   389.48 |   522.26 |     NULL |
|     7 |   478.42 |   519.05 |     NULL |
|     8 |   510.60 |   365.10 |     NULL |
|     9 |   342.67 |   340.95 |     NULL |
|    10 |   203.37 |   313.64 |     NULL |
|    11 |   130.66 |    73.19 |     NULL |
|    12 |    94.42 |     6.50 |     NULL |
+-------+----------+----------+----------+

Instiki mit Gems benutzen anstatt mit den Vendor-Plugins

Ich wollte gerne alle Gems, die ich habe, zentral mit Rubygems verwalten. Deswegen hier eine kleine Anleitung wie man Instiki dazu bekommt, diese Gems zu benutzen.

cd /path/to/instiki/vendor/
rm -r rails
cd plugins/

Es können nur die Plugins gelöscht werden, die es auch als Gems gibt.

rm -r abstract_1.0.0/ diff/ maruku/ rails_xss/ syntax/ xhtmldiff/
cd ../../

Jetzt noch die Gemfile mit folgenden Zeilen ergänzen. (Gilt für Instiki 0.19.1)

gem "rails", "2.3.10"
gem "abstract", "1.0.0"
gem "diff-lcs", "1.1.2"
gem "maruku", "0.6.0"
gem "rails_xss", "0.1.3"
gem "syntax", "1.0.0"
gem "xhtmldiff", "1.0.0"

Speichern und ein

bundle

Fertig.

DV-Cam per Firewire und ffmpeg / ffserver als FLV mit h.264 (x264) streamen

Hier ist eine Anleitung wie man eine DV-Kamera per Firewire, dvgrab, ffmpeg, ffserver und dem JWPlayer ins Netz streamen kann.

Kompiliere dir auf deinem lokalen Rechner die neueste Version von ffmpeg mit x264. Hier bekommst du ffmpeg. Wie du libx264 Support bekommst, steht (unter Anderem) hier.

Als nächstes solltest du einen Webserver zur Verfügung haben, der genug Kapazitäten (Rechenleistung/RAM/Bandbreite) hat, um dort deinen Streamingserver aufzusetzen. Dafür muss auch dort ffmpeg kompiliert werden. Bei mir gab es irgendeinen Bug, deswegen musste ich eine bestimmte Revision nutzen. Am besten du versuchst es als erstes mal mit der aktuellen Version und wenn es dort komische Fehler gibt, dann nutze die Revision 25337. Auf dem Server (bei mir Debian 5.0) also folgendes ausführen:

svn checkout -r 25337 svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg
cd ffmpeg
./configure --prefix=/usr --enable-avfilter --enable-bzlib --enable-libgsm --enable-libspeex --enable-pthreads --enable-zlib --disable-stripping --enable-runtime-cpudetect --enable-postproc --enable-swscale --enable-libdc1394 --disable-shared --enable-static --enable-nonfree --enable-libfaac --enable-libx264

Als nächstes musst du eine Konfigurationsdatei für ffserver anlegen. Diese gibt an, wie der Server erreicht werden kann und welche Streams mit welchen Eigenschaften angeboten werden.

Inhalt meiner sample.conf

# Port on which the server is listening. You must select a different
# port from your standard HTTP web server if it is running on the same
# computer.
Port 8090

# Address on which the server is bound. Only useful if you have
# several network interfaces.
BindAddress 12.34.56.789

MaxHTTPConnections 200

MaxClients 100

MaxBandwidth 10000

# Access log file (uses standard Apache log file format)
# '-' is the standard output.
CustomLog -

# Suppress that if you want to launch ffserver as a daemon.
NoDaemon

<Feed feed1.ffm>

# You must use 'ffmpeg' to send a live feed to ffserver. In this
# example, you can type:
#
# ffmpeg http://localhost:8090/feed1.ffm

File /tmp/feed1.ffm
FileMaxSize 200K
</Feed>

<Stream stream.flv>
Feed feed1.ffm
Format flv
VideoCodec libx264
AVPresetVideo default
AVPresetVideo baseline
VideoSize 512x288
VideoFrameRate 12
VideoGopSize 12
VideoBitRate 200
VideoBufferSize 40
AudioBitRate 32
AudioCodec libfaac
AudioSampleRate 22050
AVOptionVideo flags +global_header
AVOptionAudio flags +global_header
</Stream>

# Server status

<Stream stat.html>
Format status

# Only allow local people to get the status
ACL allow localhost
ACL allow 192.168.0.0 192.168.255.255

</Stream>

<Redirect index.html>
URL http://www.ffmpeg.org/
</Redirect>

Dann kann der Server mit

ffserver -f sample.conf

gestartet werden.

Jetzt solltest du auf deinem lokalen Rechner mit per Firewire angeschlossener DV-Kamera folgendes starten können. Achte darauf, dass du Zugriff auf /dev/raw1394 hast.

dvgrab - | ffmpeg -i - http://12.34.56.789:8090/feed1.ffm

Jetzt sollte dein ffmpeg/ffserver anfangen zu streamen. Du kannst dir den Status des ffservers unter

http://12.34.56.789:8090/stat.html

ansehen.

Jetzt brauchst du einen Player. Ich benutze den JWPlayer von hier. Downloaden, auf den Server ziehen, entpacken und verfügbar machen. Hier eine Beispiel HTML-Seite, die zu den ffserver Einstellungen von oben passt:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
  <head>
    <title>My Player</title>
      <script type="text/javascript" src="/jwplayer/jwplayer.js"></script>
  </head>
  <body>
        <video
        src="http://12.34.56.789:8090/stream1.flv"
        height="288"
        poster="stream1.jpg"
        id="container"
        width="512">
        </video>
        <script type="text/javascript">
          jwplayer("container").setup({
            autostart: false,
            height: 288,
            width: 512,
            image: 'http://12.34.56.789/stream1.jpg',
            players: [
          { type: "html5" },
              { type: "flash", src: "/jwplayer/player.swf" }
            ],
            streamer:'http://12.34.56.789:8090/stream1.flv'
          });
        </script>
  </body>
</html>

Jetzt solltest du diese HTML per Browser aufrufen und nach einem Klick deinen Stream sehen.

Anhang: Hier habe ich versucht ein Live-Bild zu streamen und gleichzeitig auf eine Festplatte aufzunehmen. Keine Garantie für Funktion, ist hier nur vollständigkeitshalber! :)

dvgrab - | ffmpeg -threads 4 -i - -f mpeg -r 25 -b 32000000 -s 1024x576 -acodec mp2 -ab 320000 -ar 44100 pipe: -newvideo -y -f mpeg -r 25 -b 32000000 -s 1024x576 -acodec mp2 -ab 320000 -ar 44100 /media/600c4d7c-f3b6-4a1c-abc2-181a70dfc16f/rec.mpg | ffmpeg -threads 2 -i pipe:0 http://12.34.56.789:8090/feed1.ffm